Changeset 2787042
- Timestamp:
- 09/19/2022 02:38:08 PM (4 years ago)
- Location:
- opt-out-for-google-analytics/trunk
- Files:
-
- 11 edited
-
constants.php (modified) (1 diff)
-
ga-opt-out.php (modified) (2 diffs)
-
inc/admin.class.php (modified) (12 diffs)
-
inc/promo.class.php (modified) (4 diffs)
-
inc/public.class.php (modified) (3 diffs)
-
lib/csstidy/class.csstidy.php (modified) (1 diff)
-
lib/csstidy/class.csstidy_optimise.php (modified) (1 diff)
-
lib/csstidy/class.csstidy_print.php (modified) (1 diff)
-
lib/csstidy/data.inc.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
-
templates/promotion.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
opt-out-for-google-analytics/trunk/constants.php
r2664664 r2787042 8 8 defined( 'GAOO_PLUGIN_URL' ) || define( 'GAOO_PLUGIN_URL', WP_PLUGIN_URL . DIRECTORY_SEPARATOR . GAOO_PLUGIN_NAME ); 9 9 defined( 'GAOO_PREFIX' ) || define( 'GAOO_PREFIX', '_gaoo_' ); 10 defined( 'GAOO_VERSION' ) || define( 'GAOO_VERSION', '2. 0' );10 defined( 'GAOO_VERSION' ) || define( 'GAOO_VERSION', '2.1' ); 11 11 defined( 'GAOO_SHORTCODE' ) || define( 'GAOO_SHORTCODE', '[ga_optout]' ); 12 12 defined( 'GAOO_CAPABILITY' ) || define( 'GAOO_CAPABILITY', 'manage_options' ); -
opt-out-for-google-analytics/trunk/ga-opt-out.php
r2664664 r2787042 4 4 * Plugin URI: https://www.schweizersolutions.com/?utm_source=wordpress&utm_medium=plugin&utm_campaign=plugin_uri 5 5 * Description: Adds the possibility for the user to opt out from Google Analytics. The user will not be tracked by Google Analytics on this site until he allows it again, clears his cookies or uses a different browser. 6 * Version: 2. 06 * Version: 2.1 7 7 * Author: Schweizer Solutions GmbH 8 8 * Author URI: https://www.schweizersolutions.com/?utm_source=wordpress&utm_medium=plugin&utm_campaign=author_uri … … 81 81 add_action( 'init', array( $gaoo, 'init' ) ); 82 82 add_action( 'activated_plugin', array( $gaoo, 'activated_plugin' ) ); 83 84 function gaoo_log( $data, $id = 0 ) {85 $file = GAOO_PLUGIN_DIR . "/log.txt";86 $string = '##### ' . $id . ' - ' . date( 'd.m.Y H:i:s' ) . ' ####' . PHP_EOL . var_export( $data, true ) . PHP_EOL . PHP_EOL;87 88 return ( file_put_contents( $file, $string, FILE_APPEND ) !== false );89 } -
opt-out-for-google-analytics/trunk/inc/admin.class.php
r2664664 r2787042 5 5 6 6 class GAOO_Admin { 7 7 8 private $messages; 8 9 private $csstidy; … … 14 15 */ 15 16 public function __construct() { 16 add_action( 'admin_menu', array( $this, 'menu' ) ); 17 add_action( 'admin_notices', array( $this, 'admin_notices' ) ); 18 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 19 add_action( 'save_post_page', array( $this, 'save_page' ), 10, 1 ); 20 add_action( 'gaoo_cronjob', array( $this, 'send_status_mail' ) ); 21 add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) ); 22 23 add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 2 ); 24 add_action( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 ); 17 add_action( 'admin_menu', array( 18 $this, 19 'menu' 20 ) ); 21 22 add_action( 'admin_notices', array( 23 $this, 24 'admin_notices' 25 ) ); 26 27 add_action( 'admin_enqueue_scripts', array( 28 $this, 29 'enqueue_scripts' 30 ) ); 31 32 add_action( 'save_post_page', array( 33 $this, 34 'save_page' 35 ), 10, 1 ); 36 37 add_action( 'gaoo_cronjob', array( 38 $this, 39 'send_status_mail' 40 ) ); 41 42 add_action( 'wp_dashboard_setup', array( 43 $this, 44 'add_dashboard_widget' 45 ) ); 46 47 add_action( 'upgrader_process_complete', array( 48 $this, 49 'upgrader_process_complete' 50 ), 10, 2 ); 51 52 add_action( 'admin_notices', array( 53 'GAOO_Promo', 54 'render_admin_notice' 55 ) ); 56 57 58 add_filter( 'plugin_action_links', array( 59 $this, 60 'plugin_action_links' 61 ), 10, 2 ); 62 63 add_action( 'plugin_row_meta', array( 64 $this, 65 'plugin_row_meta' 66 ), 10, 2 ); 25 67 26 68 $this->plugin = GAOO_PLUGIN_NAME . DIRECTORY_SEPARATOR . 'ga-opt-out.php'; … … 33 75 34 76 /** 77 * Handling functions after the update completed for this plugin. 78 * 79 * @param WP_Upgrader $upgrader_object WP_Upgrader instance. In other contexts this might be a Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance. 80 * @param array $options Array of bulk item update data. 81 */ 82 function upgrader_process_complete( $upgrader_object, $options ) { 83 if ( $options[ 'action' ] == 'update' && $options[ 'type' ] == 'plugin' && isset( $options[ 'plugins' ] ) && in_array( plugin_basename( __FILE__ ), $options[ 'plugins' ] ) ) { 84 GAOO_Promo::clearCache(); 85 } 86 } 87 88 /** 35 89 * Load TinyMCE filters and hooks 36 90 */ … … 45 99 } 46 100 47 add_filter( "mce_external_plugins", array( $this, 'add_tinymce_plugin' ) ); 48 add_filter( 'mce_buttons', array( $this, 'register_tinymce_button' ) ); 101 add_filter( "mce_external_plugins", array( 102 $this, 103 'add_tinymce_plugin' 104 ) ); 105 add_filter( 'mce_buttons', array( 106 $this, 107 'register_tinymce_button' 108 ) ); 49 109 } 50 110 … … 64 124 65 125 if ( ! empty( $promo ) && ! empty( $promo[ 'promo' ] ) ) { 66 $promo = array_pop( $promo[ 'promo' ] ); 67 $links[] = array( 68 'link' => $promo[ 'link' ], 69 'text' => $promo[ 'link_text' ], 70 'target' => '_blank', 71 ); 126 $promo = array_pop( $promo[ 'promo' ] ); 127 128 if ( empty( $promo[ 'hide_if_active' ] ) || ! is_array( $promo[ 'hide_if_active' ] ) || ! GAOO_Promo::should_hide( $promo[ 'hide_if_active' ] ) ) { 129 $links[] = array( 130 'link' => $promo[ 'link' ], 131 'text' => $promo[ 'link_text' ], 132 'target' => '_blank', 133 ); 134 } 72 135 } 73 136 … … 86 149 public function add_dashboard_widget() { 87 150 if ( GAOO_Utils::get_option( 'disable_dashboard' ) != 'on' ) { 88 wp_add_dashboard_widget( 'ga-opt-out', esc_html__( 'Opt-Out for Google Analytics - Status Check', 'opt-out-for-google-analytics' ), array( $this, 'render_dashboard_widget' ) ); 151 wp_add_dashboard_widget( 'ga-opt-out', esc_html__( 'Opt-Out for Google Analytics - Status Check', 'opt-out-for-google-analytics' ), array( 152 $this, 153 'render_dashboard_widget' 154 ) ); 89 155 } 90 156 } … … 134 200 */ 135 201 public function menu() { 136 add_options_page( 'Opt-Out for Google Analytics', 'GA Opt-Out', GAOO_CAPABILITY, 'gaoo', array( $this, 'menu_settings' ) ); 202 add_options_page( 'Opt-Out for Google Analytics', 'GA Opt-Out', GAOO_CAPABILITY, 'gaoo', array( 203 $this, 204 'menu_settings' 205 ) ); 137 206 } 138 207 … … 140 209 * Customize the action links. 141 210 * 142 * @param array $links Actions links.143 * @param string $file Filename of the activated plugin.211 * @param array $links Actions links. 212 * @param string $file Filename of the activated plugin. 144 213 * 145 214 * @return array Action links. … … 156 225 * Plugin row meta links 157 226 * 227 * @param array $links already defined meta links. 228 * @param string $file plugin file path and name being processed. 229 * 230 * @return array $input 158 231 * @since 1.1 159 232 * 160 * @param array $links already defined meta links.161 * @param string $file plugin file path and name being processed.162 *163 * @return array $input164 233 */ 165 234 function plugin_row_meta( $links, $file ) { 166 235 if ( $file == $this->plugin ) { 167 $links[] = "<a href='https://wordpress.org/support/plugin/opt-out-for-google-analytics/reviews/?rate=5#new-post' target='_blank'>" . esc_html__( 'Rate:', 'opt-out-for-google-analytics' ) . "<i class='gaoo-rate-stars'>" 168 . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" 169 . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" 170 . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" 171 . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" 172 . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" 173 . "</i></a>"; 236 $links[] = "<a href='https://wordpress.org/support/plugin/opt-out-for-google-analytics/reviews/?rate=5#new-post' target='_blank'>" . esc_html__( 'Rate:', 'opt-out-for-google-analytics' ) . "<i class='gaoo-rate-stars'>" . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" . "<svg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-star'><polygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2'/></svg>" . "</i></a>"; 174 237 175 238 if ( ! empty( $this->promotion ) ) { … … 206 269 207 270 if ( ! empty( $current_screen ) && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) { 208 wp_enqueue_script( 'gaoo-block', GAOO_PLUGIN_URL . '/assets/optout-block.js', array( 'wp-blocks', 'wp-editor' ), GAOO_VERSION ); 271 wp_enqueue_script( 'gaoo-block', GAOO_PLUGIN_URL . '/assets/optout-block.js', array( 272 'wp-blocks', 273 'wp-editor' 274 ), GAOO_VERSION ); 209 275 } 210 276 } … … 244 310 $ga_plugins = array( 245 311 'monsterinsights' => array( 246 'label' => ' Google Analytics for WordPress by MonsterInsights',312 'label' => 'MonsterInsights – Google Analytics Dashboard for WordPress (Website Stats Made Easy)', 247 313 'url_install' => admin_url( 'plugin-install.php?tab=search&s=Google+Analytics+MonsterInsights' ), 248 314 'url_activate' => admin_url( 'plugins.php?plugin_status=inactive&s=MonsterInsights' ), … … 274 340 ), 275 341 'sitekit' => array( 276 'label' => 'Site Kit by Google ',342 'label' => 'Site Kit by Google – Analytics, Search Console, AdSense, Speed', 277 343 'url_install' => admin_url( 'plugin-install.php?tab=search&s=Google+site+kit' ), 278 344 'url_activate' => admin_url( 'plugins.php?plugin_status=inactive&s=site+kit' ), -
opt-out-for-google-analytics/trunk/inc/promo.class.php
r2519134 r2787042 8 8 */ 9 9 class GAOO_Promo { 10 10 11 /** 11 12 * @var string $option_key_off Option name to store status … … 14 15 15 16 /** 16 * @var string $transient_key_cache T Ransient name for API cached data17 * @var string $transient_key_cache Transient name for API cached data 17 18 */ 18 19 private static $transient_key_cache = GAOO_PREFIX . 'data_cache'; 20 21 /** 22 * @var string $transient_key_notices Transient name for showing admin notices or not 23 */ 24 private static $transient_key_notices = GAOO_PREFIX . 'admin_notices_cache'; 19 25 20 26 /** … … 51 57 52 58 /** 59 * Clear cache for API response data. 60 * 61 * @return bool 62 */ 63 public static function clearCache() { 64 return delete_transient( self::$transient_key_cache ) && delete_transient( self::$transient_key_notices ); 65 } 66 67 /** 68 * Render admin notices in WP admin. 69 */ 70 public static function render_admin_notice() { 71 if ( get_transient( self::$transient_key_notices ) !== false ) { 72 return; 73 } 74 75 $data = self::get_data(); 76 77 if ( ! empty( $data[ 'promo' ] ) ) { 78 foreach ( $data[ 'promo' ] as $promo ) { 79 if ( ! empty( $promo[ 'hide_if_active' ] ) && is_array( $promo[ 'hide_if_active' ] ) && self::should_hide( $promo[ 'hide_if_active' ] ) ) { 80 continue; 81 } 82 83 echo '<div class="notice notice-info is-dismissible">' . $promo[ 'notice' ] . '</div>'; 84 } 85 86 set_transient( self::$transient_key_notices, time(), MONTH_IN_SECONDS * 3 ); 87 } 88 } 89 90 /** 91 * Check if specific plugin is activated, to hide promo. 92 * 93 * @param array $plugins Array with plugins which should be checked. 94 * 95 * @return bool True if at least one plugin is active, otherwise false. 96 */ 97 public static function should_hide( $plugins ) { 98 if ( empty( $plugins ) || ! is_array( $plugins ) ) { 99 return false; 100 } 101 102 $active_plugins = (array) get_option( 'active_plugins' ); 103 104 return ! empty( array_intersect( $plugins, $active_plugins ) ); 105 } 106 107 /** 53 108 * Render the promotion box. 54 109 * 55 110 * @param bool $pinned_only Render only the promotion which is pinned. (Default: false) 56 * @param bool $echo Echo or return the HTML code. (Default: false)57 * @param bool $popup Render the promotion in a popup box (Default: false)111 * @param bool $echo Echo or return the HTML code. (Default: false) 112 * @param bool $popup Render the promotion in a popup box (Default: false) 58 113 * 59 114 * @return string HTML code on success, otherwise empty string. … … 151 206 } 152 207 153 $data = GAOO_Utils::get_array_columns( $data[ 'promo' ], array( 'link', 'link_text' ) ); 154 $data = array_filter( $data, function ( $arr ) { 155 return array_key_exists( 'link_text', $arr ); 208 $data = array_filter( $data['promo'], function ( $arr ) { 209 if ( ! empty( $arr[ 'hide_if_active' ] ) && is_array( $arr[ 'hide_if_active' ] ) && GAOO_Promo::should_hide( $arr[ 'hide_if_active' ] ) ) { 210 211 return false; 212 } 213 214 return isset( $arr[ 'link_text' ] ); 156 215 } ); 157 216 217 $data = GAOO_Utils::get_array_columns( $data, array( 218 'link', 219 'link_text' 220 ) ); 221 158 222 return empty( $data ) ? null : $data; 159 223 } 224 160 225 } -
opt-out-for-google-analytics/trunk/inc/public.class.php
r2664664 r2787042 5 5 6 6 class GAOO_Public { 7 7 8 private $csstidy; 8 9 … … 11 12 */ 12 13 public function __construct() { 13 add_action( 'wp_head', array( $this, 'head_script' ), -1 ); 14 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 14 add_action( 'wp_head', array( 15 $this, 16 'head_script' 17 ), -1 ); 15 18 16 add_shortcode( 'ga_optout', array( $this, 'shortcode' ) ); 19 add_action( 'wp_enqueue_scripts', array( 20 $this, 21 'enqueue_scripts' 22 ) ); 23 24 add_shortcode( 'ga_optout', array( 25 $this, 26 'shortcode' 27 ) ); 17 28 18 29 if ( has_filter( 'widget_text', 'do_shortcode' ) !== false ) { … … 84 95 85 96 if ( ! empty( $form_data[ 'tracking_code' ] ) ) { 86 echo preg_replace( '/\s+/', ' ', stripslashes( $form_data[ 'tracking_code'] ) );97 echo preg_replace( '/\s+/', ' ', stripslashes( $form_data[ 'tracking_code' ] ) ); 87 98 } 88 99 -
opt-out-for-google-analytics/trunk/lib/csstidy/class.csstidy.php
r2518394 r2787042 1 1 <?php 2 // If this file is called directly, abort. 3 defined( 'WPINC' ) || die; 4 5 /** 6 * CSSTidy - CSS Parser and Optimiser 7 * 8 * CSS Parser class 9 * 10 * Copyright 2005, 2006, 2007 Florian Schmitz 11 * 12 * This file is part of CSSTidy. 13 * 14 * CSSTidy is free software; you can redistribute it and/or modify 15 * it under the terms of the GNU Lesser General Public License as published by 16 * the Free Software Foundation; either version 2.1 of the License, or 17 * (at your option) any later version. 18 * 19 * CSSTidy is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU Lesser General Public License for more details. 23 * 24 * You should have received a copy of the GNU Lesser General Public License 25 * along with this program. If not, see <http://www.gnu.org/licenses/>. 26 * 27 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 28 * @package csstidy 29 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 30 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 31 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 32 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 33 * @author Christopher Finke (cfinke at gmail.com) 2012 34 * @author Mark Scherer (remove $GLOBALS once and for all + PHP5.4 comp) 2012 35 */ 36 37 /** 38 * Defines constants 39 * @todo //TODO: make them class constants of csstidy 40 */ 41 define( 'AT_START', 1 ); 42 define( 'AT_END', 2 ); 43 define( 'SEL_START', 3 ); 44 define( 'SEL_END', 4 ); 45 define( 'PROPERTY', 5 ); 46 define( 'VALUE', 6 ); 47 define( 'COMMENT', 7 ); 48 define( 'IMPORTANT_COMMENT', 8 ); 49 define( 'DEFAULT_AT', 41 ); 50 51 /** 52 * Contains a class for printing CSS code 53 * 54 * @version 1.1.0 55 */ 56 require( 'class.csstidy_print.php' ); 57 58 /** 59 * Contains a class for optimising CSS code 60 * 61 * @version 1.0 62 */ 63 require( 'class.csstidy_optimise.php' ); 64 65 /** 66 * CSS Parser class 67 * 68 * This class represents a CSS parser which reads CSS code and saves it in an array. 69 * In opposite to most other CSS parsers, it does not use regular expressions and 70 * thus has full CSS2 support and a higher reliability. 71 * Additional to that it applies some optimisations and fixes to the CSS code. 72 * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php 73 * @package csstidy 74 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 75 * @version 1.7.3 76 */ 77 class csstidy { 78 79 /** 80 * Saves the parsed CSS. This array is empty if preserve_css is on. 81 * @var array 82 * @access public 83 */ 84 public $css = array(); 85 /** 86 * Saves the parsed CSS (raw) 87 * @var array 88 * @access private 89 */ 90 public $tokens = array(); 91 /** 92 * Printer class 93 * @see csstidy_print 94 * @var object 95 * @access public 96 */ 97 public $print; 98 /** 99 * Optimiser class 100 * @see csstidy_optimise 101 * @var object 102 * @access private 103 */ 104 public $optimise; 105 /** 106 * Saves the CSS charset (@charset) 107 * @var string 108 * @access private 109 */ 110 public $charset = ''; 111 /** 112 * Saves all @import URLs 113 * @var array 114 * @access private 115 */ 116 public $import = array(); 117 /** 118 * Saves the namespace 119 * @var string 120 * @access private 121 */ 122 public $namespace = ''; 123 /** 124 * Contains the version of csstidy 125 * @var string 126 * @access private 127 */ 128 public $version = '1.7.3'; 129 /** 130 * Stores the settings 131 * @var array 132 * @access private 133 */ 134 public $settings = array(); 135 /** 136 * Saves the parser-status. 137 * 138 * Possible values: 139 * - is = in selector 140 * - ip = in property 141 * - iv = in value 142 * - instr = in string (started at " or ' or ( ) 143 * - ic = in comment (ignore everything) 144 * - at = in @-block 145 * 146 * @var string 147 * @access private 148 */ 149 public $status = 'is'; 150 /** 151 * Saves the current at rule (@media) 152 * @var string 153 * @access private 154 */ 155 public $at = ''; 156 /** 157 * Saves the at rule for next selector (during @font-face or other @) 158 * @var string 159 * @access private 160 */ 161 public $next_selector_at = ''; 162 163 /** 164 * Saves the current selector 165 * @var string 166 * @access private 167 */ 168 public $selector = ''; 169 /** 170 * Saves the current property 171 * @var string 172 * @access private 173 */ 174 public $property = ''; 175 /** 176 * Saves the position of , in selectors 177 * @var array 178 * @access private 179 */ 180 public $sel_separate = array(); 181 /** 182 * Saves the current value 183 * @var string 184 * @access private 185 */ 186 public $value = ''; 187 /** 188 * Saves the current sub-value 189 * 190 * Example for a subvalue: 191 * background:url(foo.png) red no-repeat; 192 * "url(foo.png)", "red", and "no-repeat" are subvalues, 193 * seperated by whitespace 194 * @var string 195 * @access private 196 */ 197 public $sub_value = ''; 198 /** 199 * Array which saves all subvalues for a property. 200 * @var array 201 * @see sub_value 202 * @access private 203 */ 204 public $sub_value_arr = array(); 205 /** 206 * Saves the stack of characters that opened the current strings 207 * @var array 208 * @access private 209 */ 210 public $str_char = array(); 211 public $cur_string = array(); 212 /** 213 * Status from which the parser switched to ic or instr 214 * @var array 215 * @access private 216 */ 217 public $from = array(); 218 /** 219 * /** 220 * =true if in invalid at-rule 221 * @var bool 222 * @access private 223 */ 224 public $invalid_at = false; 225 /** 226 * =true if something has been added to the current selector 227 * @var bool 228 * @access private 229 */ 230 public $added = false; 231 /** 232 * Array which saves the message log 233 * @var array 234 * @access private 235 */ 236 public $log = array(); 237 /** 238 * Saves the line number 239 * @var integer 240 * @access private 241 */ 242 public $line = 1; 243 /** 244 * Marks if we need to leave quotes for a string 245 * @var array 246 * @access private 247 */ 248 public $quoted_string = array(); 249 250 /** 251 * List of tokens 252 * @var string 253 */ 254 public $tokens_list = ""; 255 256 /** 257 * Various CSS Data for CSSTidy 258 * @var array 259 */ 260 public $data = array(); 261 262 /** 263 * Loads standard template and sets default settings 264 * @access private 265 * @version 1.3 266 */ 267 public function __construct() { 268 $data = array(); 269 include( 'data.inc.php' ); 270 $this->data = $data; 271 272 $this->settings[ 'remove_bslash' ] = true; 273 $this->settings[ 'compress_colors' ] = true; 274 $this->settings[ 'compress_font-weight' ] = true; 275 $this->settings[ 'lowercase_s' ] = false; 276 /* 2 3 /** 4 * CSSTidy - CSS Parser and Optimiser 5 * 6 * CSS Parser class 7 * 8 * Copyright 2005, 2006, 2007 Florian Schmitz 9 * 10 * This file is part of CSSTidy. 11 * 12 * CSSTidy is free software; you can redistribute it and/or modify 13 * it under the terms of the GNU Lesser General Public License as published by 14 * the Free Software Foundation; either version 2.1 of the License, or 15 * (at your option) any later version. 16 * 17 * CSSTidy is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU Lesser General Public License for more details. 21 * 22 * You should have received a copy of the GNU Lesser General Public License 23 * along with this program. If not, see <http://www.gnu.org/licenses/>. 24 * 25 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 26 * @package csstidy 27 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 28 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 29 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 30 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 31 * @author Christopher Finke (cfinke at gmail.com) 2012 32 * @author Mark Scherer (remove $GLOBALS once and for all + PHP5.4 comp) 2012 33 */ 34 35 /** 36 * Defines constants 37 * @todo //TODO: make them class constants of csstidy 38 */ 39 define('AT_START', 1); 40 define('AT_END', 2); 41 define('SEL_START', 3); 42 define('SEL_END', 4); 43 define('PROPERTY', 5); 44 define('VALUE', 6); 45 define('COMMENT', 7); 46 define('IMPORTANT_COMMENT',8); 47 define('DEFAULT_AT', 41); 48 49 /** 50 * Contains a class for printing CSS code 51 * 52 * @version 1.1.0 53 */ 54 require('class.csstidy_print.php'); 55 56 /** 57 * Contains a class for optimising CSS code 58 * 59 * @version 1.0 60 */ 61 require('class.csstidy_optimise.php'); 62 63 /** 64 * CSS Parser class 65 * 66 * This class represents a CSS parser which reads CSS code and saves it in an array. 67 * In opposite to most other CSS parsers, it does not use regular expressions and 68 * thus has full CSS2 support and a higher reliability. 69 * Additional to that it applies some optimisations and fixes to the CSS code. 70 * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php 71 * @package csstidy 72 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 73 * @version 2.0.0 74 */ 75 class csstidy { 76 77 /** 78 * Saves the parsed CSS. This array is empty if preserve_css is on. 79 * @var array 80 * @access public 81 */ 82 public $css = array(); 83 /** 84 * Saves the parsed CSS (raw) 85 * @var array 86 * @access private 87 */ 88 public $tokens = array(); 89 /** 90 * Printer class 91 * @see csstidy_print 92 * @var object 93 * @access public 94 */ 95 public $print; 96 /** 97 * Optimiser class 98 * @see csstidy_optimise 99 * @var object 100 * @access private 101 */ 102 public $optimise; 103 /** 104 * Saves the CSS charset (@charset) 105 * @var string 106 * @access private 107 */ 108 public $charset = ''; 109 /** 110 * Saves all @import URLs 111 * @var array 112 * @access private 113 */ 114 public $import = array(); 115 /** 116 * Saves the namespace 117 * @var string 118 * @access private 119 */ 120 public $namespace = ''; 121 /** 122 * Contains the version of csstidy 123 * @var string 124 * @access private 125 */ 126 public $version = '1.7.3'; 127 /** 128 * Stores the settings 129 * @var array 130 * @access private 131 */ 132 public $settings = array(); 133 /** 134 * Saves the parser-status. 135 * 136 * Possible values: 137 * - is = in selector 138 * - ip = in property 139 * - iv = in value 140 * - instr = in string (started at " or ' or ( ) 141 * - ic = in comment (ignore everything) 142 * - at = in @-block 143 * 144 * @var string 145 * @access private 146 */ 147 public $status = 'is'; 148 /** 149 * Saves the current at rule (@media) 150 * @var string 151 * @access private 152 */ 153 public $at = ''; 154 /** 155 * Saves the at rule for next selector (during @font-face or other @) 156 * @var string 157 * @access private 158 */ 159 public $next_selector_at = ''; 160 161 /** 162 * Saves the current selector 163 * @var string 164 * @access private 165 */ 166 public $selector = ''; 167 /** 168 * Saves the current property 169 * @var string 170 * @access private 171 */ 172 public $property = ''; 173 /** 174 * Saves the position of , in selectors 175 * @var array 176 * @access private 177 */ 178 public $sel_separate = array(); 179 /** 180 * Saves the current value 181 * @var string 182 * @access private 183 */ 184 public $value = ''; 185 /** 186 * Saves the current sub-value 187 * 188 * Example for a subvalue: 189 * background:url(foo.png) red no-repeat; 190 * "url(foo.png)", "red", and "no-repeat" are subvalues, 191 * seperated by whitespace 192 * @var string 193 * @access private 194 */ 195 public $sub_value = ''; 196 /** 197 * Array which saves all subvalues for a property. 198 * @var array 199 * @see sub_value 200 * @access private 201 */ 202 public $sub_value_arr = array(); 203 /** 204 * Saves the stack of characters that opened the current strings 205 * @var array 206 * @access private 207 */ 208 public $str_char = array(); 209 public $cur_string = array(); 210 /** 211 * Status from which the parser switched to ic or instr 212 * @var array 213 * @access private 214 */ 215 public $from = array(); 216 /** 217 /** 218 * =true if in invalid at-rule 219 * @var bool 220 * @access private 221 */ 222 public $invalid_at = false; 223 /** 224 * =true if something has been added to the current selector 225 * @var bool 226 * @access private 227 */ 228 public $added = false; 229 /** 230 * Array which saves the message log 231 * @var array 232 * @access private 233 */ 234 public $log = array(); 235 /** 236 * Saves the line number 237 * @var integer 238 * @access private 239 */ 240 public $line = 1; 241 /** 242 * Marks if we need to leave quotes for a string 243 * @var array 244 * @access private 245 */ 246 public $quoted_string = array(); 247 248 /** 249 * List of tokens 250 * @var string 251 */ 252 public $tokens_list = ""; 253 254 /** 255 * Various CSS Data for CSSTidy 256 * @var array 257 */ 258 public $data = array(); 259 260 /** 261 * Loads standard template and sets default settings 262 * @access private 263 * @version 1.3 264 */ 265 public function __construct() { 266 $data = array(); 267 include('data.inc.php'); 268 $this->data = $data; 269 270 $this->settings['remove_bslash'] = true; 271 $this->settings['compress_colors'] = true; 272 $this->settings['compress_font-weight'] = true; 273 $this->settings['lowercase_s'] = false; 274 /* 277 275 1 common shorthands optimization 278 276 2 + font property optimization 279 277 3 + background property optimization 280 278 */ 281 $this->settings[ 'optimise_shorthands' ]= 1;282 $this->settings[ 'remove_last_;' ]= true;283 $this->settings[ 'space_before_important'] = false;284 /* rewrite all properties with low case, better for later gzip OK, safe*/285 $this->settings[ 'case_properties'] = 1;286 /* sort properties in alpabetic order, better for later gzip279 $this->settings['optimise_shorthands'] = 1; 280 $this->settings['remove_last_;'] = true; 281 $this->settings['space_before_important'] = false; 282 /* rewrite all properties with low case, better for later gzip OK, safe*/ 283 $this->settings['case_properties'] = 1; 284 /* sort properties in alpabetic order, better for later gzip 287 285 * but can cause trouble in case of overiding same propertie or using hack 288 286 */ 289 $this->settings[ 'sort_properties'] = false;290 /*287 $this->settings['sort_properties'] = false; 288 /* 291 289 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{} 292 290 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{} 293 291 preserve order by default cause it can break functionnality 294 292 */ 295 $this->settings[ 'sort_selectors' ] = 0; 296 /* is dangeroues to be used: CSS is broken sometimes */ 297 $this->settings[ 'merge_selectors' ] = 0; 298 /* preserve or not browser hacks */ 299 300 /* Useful to produce a rtl css from a ltr one (or the opposite) */ 301 $this->settings[ 'reverse_left_and_right' ] = 0; 302 303 $this->settings[ 'discard_invalid_selectors' ] = false; 304 $this->settings[ 'discard_invalid_properties' ] = false; 305 $this->settings[ 'css_level' ] = 'CSS3.0'; 306 $this->settings[ 'preserve_css' ] = false; 307 $this->settings[ 'timestamp' ] = false; 308 $this->settings[ 'template' ] = ''; // say that propertie exist 309 $this->set_cfg( 'template', 'default' ); // call load_template 310 $this->optimise = new csstidy_optimise( $this ); 311 312 $this->tokens_list = &$this->data[ 'csstidy' ][ 'tokens' ]; 313 } 314 315 /** 316 * Get the value of a setting. 317 * 318 * @param string $setting 319 * 320 * @access public 321 * @return mixed 322 * @version 1.0 323 */ 324 public function get_cfg( $setting ) { 325 if ( isset( $this->settings[ $setting ] ) ) { 326 return $this->settings[ $setting ]; 327 } 328 return false; 329 } 330 331 /** 332 * Load a template 333 * 334 * @param string $template used by set_cfg to load a template via a configuration setting 335 * 336 * @access private 337 * @version 1.4 338 */ 339 public function _load_template( $template ) { 340 switch ( $template ) { 341 case 'default': 342 $this->load_template( 'default' ); 343 break; 344 345 case 'highest': 346 $this->load_template( 'highest_compression' ); 347 break; 348 349 case 'high': 350 $this->load_template( 'high_compression' ); 351 break; 352 353 case 'low': 354 $this->load_template( 'low_compression' ); 355 break; 356 357 default: 358 $this->load_template( $template ); 359 break; 360 } 361 } 362 363 /** 364 * Set the value of a setting. 365 * 366 * @param string $setting 367 * @param mixed $value 368 * 369 * @access public 370 * @return bool 371 * @version 1.0 372 */ 373 public function set_cfg( $setting, $value = null ) { 374 if ( is_array( $setting ) && $value === null ) { 375 foreach ( $setting as $setprop => $setval ) { 376 $this->settings[ $setprop ] = $setval; 377 } 378 if ( array_key_exists( 'template', $setting ) ) { 379 $this->_load_template( $this->settings[ 'template' ] ); 380 } 381 return true; 382 } 383 elseif ( isset( $this->settings[ $setting ] ) && $value !== '' ) { 384 $this->settings[ $setting ] = $value; 385 if ( $setting === 'template' ) { 386 $this->_load_template( $this->settings[ 'template' ] ); 387 } 388 return true; 389 } 390 return false; 391 } 392 393 /** 394 * Adds a token to $this->tokens 395 * 396 * @param mixed $type 397 * @param string $data 398 * @param bool $do add a token even if preserve_css is off 399 * 400 * @access private 401 * @version 1.0 402 */ 403 public function _add_token( $type, $data, $do = false ) { 404 if ( $this->get_cfg( 'preserve_css' ) || $do ) { 405 // nested @... : if opening a new part we just closed, remove the previous closing instead of adding opening 406 if ( $type === AT_START 407 and count( $this->tokens ) 408 and $last = end( $this->tokens ) 409 and $last[ 0 ] === AT_END 410 and $last[ 1 ] === trim( $data ) ) { 411 array_pop( $this->tokens ); 412 } 413 else { 414 $this->tokens[] = array( $type, ( $type == COMMENT or $type == IMPORTANT_COMMENT ) ? $data : trim( $data ) ); 415 } 416 } 417 } 418 419 /** 420 * Add a message to the message log 421 * 422 * @param string $message 423 * @param string $type 424 * @param integer $line 425 * 426 * @access private 427 * @version 1.0 428 */ 429 public function log( $message, $type, $line = -1 ) { 430 if ( $line === -1 ) { 431 $line = $this->line; 432 } 433 $line = intval( $line ); 434 $add = array( 'm' => $message, 't' => $type ); 435 if ( ! isset( $this->log[ $line ] ) || ! in_array( $add, $this->log[ $line ] ) ) { 436 $this->log[ $line ][] = $add; 437 } 438 } 439 440 /** 441 * Parse unicode notations and find a replacement character 442 * 443 * @param string $string 444 * @param integer $i 445 * 446 * @access private 447 * @return string 448 * @version 1.2 449 */ 450 public function _unicode( &$string, &$i ) { 451 ++$i; 452 $add = ''; 453 $replaced = false; 454 455 while ( $i < strlen( $string ) && ( ctype_xdigit( $string[ $i ] ) || ctype_space( $string[ $i ] ) ) && strlen( $add ) < 6 ) { 456 $add .= $string[ $i ]; 457 458 if ( ctype_space( $string[ $i ] ) ) { 459 break; 460 } 461 $i++; 462 } 463 464 if ( hexdec( $add ) > 47 && hexdec( $add ) < 58 || hexdec( $add ) > 64 && hexdec( $add ) < 91 || hexdec( $add ) > 96 && hexdec( $add ) < 123 ) { 465 $this->log( 'Replaced unicode notation: Changed \\' . $add . ' to ' . chr( hexdec( $add ) ), 'Information' ); 466 $add = chr( hexdec( $add ) ); 467 $replaced = true; 468 } 469 else { 470 $add = trim( '\\' . $add ); 471 } 472 473 if ( @ctype_xdigit( $string[ $i + 1 ] ) && ctype_space( $string[ $i ] ) 474 && ! $replaced || ! ctype_space( $string[ $i ] ) ) { 475 $i--; 476 } 477 478 if ( $add !== '\\' || ! $this->get_cfg( 'remove_bslash' ) || strpos( $this->tokens_list, $string[ $i + 1 ] ) !== false ) { 479 return $add; 480 } 481 482 if ( $add === '\\' ) { 483 $this->log( 'Removed unnecessary backslash', 'Information' ); 484 } 485 return ''; 486 } 487 488 /** 489 * Write formatted output to a file 490 * 491 * @param string $filename 492 * @param string $doctype when printing formatted, is a shorthand for the document type 493 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet 494 * @param string $title when printing formatted, is the title to be added in the head of the document 495 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output 496 * 497 * @access public 498 * @version 1.4 499 */ 500 public function write_page( $filename, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en' ) { 501 $this->write( $filename, true ); 502 } 503 504 /** 505 * Write plain output to a file 506 * 507 * @param string $filename 508 * @param bool $formatted whether to print formatted or not 509 * @param string $doctype when printing formatted, is a shorthand for the document type 510 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet 511 * @param string $title when printing formatted, is the title to be added in the head of the document 512 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output 513 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates) 514 * 515 * @access public 516 * @version 1.4 517 */ 518 public function write( $filename, $formatted = false, $doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en', $pre_code = true ) { 519 $filename .= ( $formatted ) ? '.xhtml' : '.css'; 520 521 if ( ! is_dir( 'temp' ) ) { 522 $madedir = mkdir( 'temp' ); 523 if ( ! $madedir ) { 524 print 'Could not make directory "temp" in ' . dirname( __FILE__ ); 525 exit; 526 } 527 } 528 $handle = fopen( 'temp/' . $filename, 'w' ); 529 if ( $handle ) { 530 if ( ! $formatted ) { 531 fwrite( $handle, $this->print->plain() ); 532 } 533 else { 534 fwrite( $handle, $this->print->formatted_page( $doctype, $externalcss, $title, $lang, $pre_code ) ); 535 } 536 } 537 fclose( $handle ); 538 } 539 540 /** 541 * Loads a new template 542 * 543 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default" 544 * @param bool $from_file uses $content as filename if true 545 * 546 * @access public 547 * @version 1.1 548 * @see http://csstidy.sourceforge.net/templates.php 549 */ 550 public function load_template( $content, $from_file = true ) { 551 $predefined_templates = &$this->data[ 'csstidy' ][ 'predefined_templates' ]; 552 if ( $content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression' ) { 553 $this->template = $predefined_templates[ $content ]; 554 return; 555 } 556 557 558 if ( $from_file ) { 559 $content = strip_tags( file_get_contents( $content ), '<span>' ); 560 } 561 $content = str_replace( "\r\n", "\n", $content ); // Unify newlines (because the output also only uses \n) 562 $template = explode( '|', $content ); 563 564 for ( $i = 0; $i < count( $template ); $i++ ) { 565 $this->template[ $i ] = $template[ $i ]; 566 } 567 } 568 569 /** 570 * Starts parsing from URL 571 * 572 * @param string $url 573 * 574 * @access public 575 * @version 1.0 576 */ 577 public function parse_from_url( $url ) { 578 return $this->parse( @file_get_contents( $url ) ); 579 } 580 581 /** 582 * Checks if there is a token at the current position 583 * 584 * @param string $string 585 * @param integer $i 586 * 587 * @access public 588 * @version 1.11 589 */ 590 public function is_token( &$string, $i ) { 591 return ( strpos( $this->tokens_list, $string[ $i ] ) !== false && ! $this->escaped( $string, $i ) ); 592 } 593 594 /** 595 * Parses CSS in $string. The code is saved as array in $this->css 596 * 597 * @param string $string the CSS code 598 * 599 * @access public 600 * @return bool 601 * @version 1.1 602 */ 603 public function parse( $string ) { 604 // Temporarily set locale to en_US in order to handle floats properly 605 $old = @setlocale( LC_ALL, 0 ); 606 @setlocale( LC_ALL, 'C' ); 607 608 // PHP bug? Settings need to be refreshed in PHP4 609 $this->print = new csstidy_print( $this ); 610 $this->optimise = new csstidy_optimise( $this ); 611 612 $all_properties = &$this->data[ 'csstidy' ][ 'all_properties' ]; 613 $at_rules = &$this->data[ 'csstidy' ][ 'at_rules' ]; 614 $quoted_string_properties = &$this->data[ 'csstidy' ][ 'quoted_string_properties' ]; 615 616 $this->css = array(); 617 $this->print->input_css = $string; 618 $string = str_replace( "\r\n", "\n", $string ) . ' '; 619 $cur_comment = ''; 620 $cur_at = ''; 621 622 for ( $i = 0, $size = strlen( $string ); $i < $size; $i++ ) { 623 if ( $string[ $i ] === "\n" || $string[ $i ] === "\r" ) { 624 ++$this->line; 625 } 626 627 switch ( $this->status ) { 628 /* Case in at-block */ 629 case 'at': 630 if ( $this->is_token( $string, $i ) ) { 631 if ( $string[ $i ] === '/' && @$string[ $i + 1 ] === '*' ) { 632 $this->status = 'ic'; 633 ++$i; 634 $this->from[] = 'at'; 635 } 636 elseif ( $string[ $i ] === '{' ) { 637 $this->status = 'is'; 638 $this->at = $this->css_new_media_section( $this->at, $cur_at ); 639 $this->_add_token( AT_START, $this->at ); 640 } 641 elseif ( $string[ $i ] === ',' ) { 642 $cur_at = trim( $cur_at ) . ','; 643 } 644 elseif ( $string[ $i ] === '\\' ) { 645 $cur_at .= $this->_unicode( $string, $i ); 646 } 647 // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5) 648 elseif ( in_array( $string[ $i ], array( '(', ')', ':', '.', '/' ) ) ) { 649 $cur_at .= $string[ $i ]; 650 } 651 } 652 else { 653 $lastpos = strlen( $cur_at ) - 1; 654 if ( ! ( ( ctype_space( $cur_at[ $lastpos ] ) || $this->is_token( $cur_at, $lastpos ) && $cur_at[ $lastpos ] === ',' ) && ctype_space( $string[ $i ] ) ) ) { 655 $cur_at .= $string[ $i ]; 656 } 657 } 658 break; 659 660 /* Case in-selector */ 661 case 'is': 662 if ( $this->is_token( $string, $i ) ) { 663 if ( $string[ $i ] === '/' && @$string[ $i + 1 ] === '*' && trim( $this->selector ) == '' ) { 664 $this->status = 'ic'; 665 ++$i; 666 $this->from[] = 'is'; 667 } 668 elseif ( $string[ $i ] === '@' && trim( $this->selector ) == '' ) { 669 // Check for at-rule 670 $this->invalid_at = true; 671 foreach ( $at_rules as $name => $type ) { 672 if ( ! strcasecmp( substr( $string, $i + 1, strlen( $name ) ), $name ) ) { 673 ( $type === 'at' ) ? $cur_at = '@' . $name : $this->selector = '@' . $name; 674 if ( $type === 'atis' ) { 675 $this->next_selector_at = ( $this->next_selector_at ? $this->next_selector_at : ( $this->at ? $this->at : DEFAULT_AT ) ); 676 $this->at = $this->css_new_media_section( $this->at, ' ', true ); 677 $type = 'is'; 678 } 679 $this->status = $type; 680 $i += strlen( $name ); 681 $this->invalid_at = false; 682 break; 683 } 684 } 685 686 if ( $this->invalid_at ) { 687 $this->selector = '@'; 688 $invalid_at_name = ''; 689 for ( $j = $i + 1; $j < $size; ++$j ) { 690 if ( ! ctype_alpha( $string[ $j ] ) ) { 691 break; 692 } 693 $invalid_at_name .= $string[ $j ]; 694 } 695 $this->log( 'Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning' ); 696 } 697 } 698 elseif ( ( $string[ $i ] === '"' || $string[ $i ] === "'" ) ) { 699 $this->cur_string[] = $string[ $i ]; 700 $this->status = 'instr'; 701 $this->str_char[] = $string[ $i ]; 702 $this->from[] = 'is'; 703 /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */ 704 $this->quoted_string[] = ( $string[ $i - 1 ] === '=' ); 705 } 706 elseif ( $this->invalid_at && $string[ $i ] === ';' ) { 707 $this->invalid_at = false; 708 $this->status = 'is'; 709 if ( $this->next_selector_at ) { 710 $this->at = $this->css_close_media_section( $this->at ); 711 $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); 712 $this->next_selector_at = ''; 713 } 714 } 715 elseif ( $string[ $i ] === '{' ) { 716 $this->status = 'ip'; 717 if ( $this->at == '' ) { 718 $this->at = $this->css_new_media_section( $this->at, DEFAULT_AT ); 719 } 720 $this->selector = $this->css_new_selector( $this->at, $this->selector ); 721 $this->_add_token( SEL_START, $this->selector ); 722 $this->added = false; 723 } 724 elseif ( $string[ $i ] === '}' ) { 725 $this->_add_token( AT_END, $this->at ); 726 $this->at = $this->css_close_media_section( $this->at ); 727 $this->selector = ''; 728 $this->sel_separate = array(); 729 } 730 elseif ( $string[ $i ] === ',' ) { 731 $this->selector = trim( $this->selector ) . ','; 732 $this->sel_separate[] = strlen( $this->selector ); 733 } 734 elseif ( $string[ $i ] === '\\' ) { 735 $this->selector .= $this->_unicode( $string, $i ); 736 } 737 elseif ( $string[ $i ] === '*' && @in_array( $string[ $i + 1 ], array( '.', '#', '[', ':' ) ) && ( $i == 0 OR $string[ $i - 1 ] !== '/' ) ) { 738 // remove unnecessary universal selector, FS#147, but not comment in selector 739 } 740 else { 741 $this->selector .= $string[ $i ]; 742 } 743 } 744 else { 745 $lastpos = strlen( $this->selector ) - 1; 746 if ( $lastpos == -1 || ! ( ( ctype_space( $this->selector[ $lastpos ] ) || $this->is_token( $this->selector, $lastpos ) && $this->selector[ $lastpos ] === ',' ) && ctype_space( $string[ $i ] ) ) ) { 747 $this->selector .= $string[ $i ]; 748 } 749 } 750 break; 751 752 /* Case in-property */ 753 case 'ip': 754 if ( $this->is_token( $string, $i ) ) { 755 if ( ( $string[ $i ] === ':' || $string[ $i ] === '=' ) && $this->property != '' ) { 756 $this->status = 'iv'; 757 if ( ! $this->get_cfg( 'discard_invalid_properties' ) || $this->property_is_valid( $this->property ) ) { 758 $this->property = $this->css_new_property( $this->at, $this->selector, $this->property ); 759 $this->_add_token( PROPERTY, $this->property ); 760 } 761 } 762 elseif ( $string[ $i ] === '/' && @$string[ $i + 1 ] === '*' && $this->property == '' ) { 763 $this->status = 'ic'; 764 ++$i; 765 $this->from[] = 'ip'; 766 } 767 elseif ( $string[ $i ] === '}' ) { 768 $this->explode_selectors(); 769 $this->status = 'is'; 770 $this->invalid_at = false; 771 $this->_add_token( SEL_END, $this->selector ); 772 $this->selector = ''; 773 $this->property = ''; 774 if ( $this->next_selector_at ) { 775 $this->at = $this->css_close_media_section( $this->at ); 776 $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); 777 $this->next_selector_at = ''; 778 } 779 } 780 elseif ( $string[ $i ] === ';' ) { 781 $this->property = ''; 782 } 783 elseif ( $string[ $i ] === '\\' ) { 784 $this->property .= $this->_unicode( $string, $i ); 785 } 786 // else this is dumb IE a hack, keep it 787 // including // 788 elseif ( ( $this->property === '' && ! ctype_space( $string[ $i ] ) ) 789 || ( $this->property === '/' || $string[ $i ] === '/' ) ) { 790 $this->property .= $string[ $i ]; 791 } 792 } 793 elseif ( ! ctype_space( $string[ $i ] ) ) { 794 $this->property .= $string[ $i ]; 795 } 796 break; 797 798 /* Case in-value */ 799 case 'iv': 800 $pn = ( ( $string[ $i ] === "\n" || $string[ $i ] === "\r" ) && $this->property_is_next( $string, $i + 1 ) || $i == strlen( $string ) - 1 ); 801 if ( $this->is_token( $string, $i ) || $pn ) { 802 if ( $string[ $i ] === '/' && @$string[ $i + 1 ] === '*' ) { 803 $this->status = 'ic'; 804 ++$i; 805 $this->from[] = 'iv'; 806 } 807 elseif ( ( $string[ $i ] === '"' || $string[ $i ] === "'" || $string[ $i ] === '(' ) ) { 808 $this->cur_string[] = $string[ $i ]; 809 $this->str_char[] = ( $string[ $i ] === '(' ) ? ')' : $string[ $i ]; 810 $this->status = 'instr'; 811 $this->from[] = 'iv'; 812 $this->quoted_string[] = in_array( strtolower( $this->property ), $quoted_string_properties ); 813 } 814 elseif ( $string[ $i ] === ',' ) { 815 $this->sub_value = trim( $this->sub_value ) . ','; 816 } 817 elseif ( $string[ $i ] === '\\' ) { 818 $this->sub_value .= $this->_unicode( $string, $i ); 819 } 820 elseif ( $string[ $i ] === ';' || $pn ) { 821 if ( $this->selector[ 0 ] === '@' && isset( $at_rules[ substr( $this->selector, 1 ) ] ) && $at_rules[ substr( $this->selector, 1 ) ] === 'iv' ) { 822 /* Add quotes to charset, import, namespace */ 823 $this->sub_value_arr[] = trim( $this->sub_value ); 824 825 $this->status = 'is'; 826 827 switch ( $this->selector ) { 828 case '@charset': 829 $this->charset = '"' . $this->sub_value_arr[ 0 ] . '"'; 830 break; 831 case '@namespace': 832 $this->namespace = implode( ' ', $this->sub_value_arr ); 833 break; 834 case '@import': 835 $this->import[] = implode( ' ', $this->sub_value_arr ); 836 break; 837 } 838 839 $this->sub_value_arr = array(); 840 $this->sub_value = ''; 841 $this->selector = ''; 842 $this->sel_separate = array(); 843 } 844 else { 845 $this->status = 'ip'; 846 } 847 } 848 elseif ( $string[ $i ] !== '}' ) { 849 $this->sub_value .= $string[ $i ]; 850 } 851 if ( ( $string[ $i ] === '}' || $string[ $i ] === ';' || $pn ) && ! empty( $this->selector ) ) { 852 if ( $this->at == '' ) { 853 $this->at = $this->css_new_media_section( $this->at, DEFAULT_AT ); 854 } 855 856 // case settings 857 if ( $this->get_cfg( 'lowercase_s' ) ) { 858 $this->selector = strtolower( $this->selector ); 859 } 860 $this->property = strtolower( $this->property ); 861 862 $this->optimise->subvalue(); 863 if ( $this->sub_value != '' ) { 864 $this->sub_value_arr[] = $this->sub_value; 865 $this->sub_value = ''; 866 } 867 868 $this->value = ''; 869 while ( count( $this->sub_value_arr ) ) { 870 $sub = array_shift( $this->sub_value_arr ); 871 if ( strstr( $this->selector, 'font-face' ) ) { 872 $sub = $this->quote_font_format( $sub ); 873 } 874 875 if ( $sub != '' ) { 876 $this->value .= ( ( ! strlen( $this->value ) || substr( $this->value, -1, 1 ) === ',' ) ? '' : ' ' ) . $sub; 877 } 878 } 879 880 $this->optimise->value(); 881 882 $valid = $this->property_is_valid( $this->property ); 883 if ( ( ! $this->invalid_at || $this->get_cfg( 'preserve_css' ) ) && ( ! $this->get_cfg( 'discard_invalid_properties' ) || $valid ) ) { 884 $this->css_add_property( $this->at, $this->selector, $this->property, $this->value ); 885 $this->_add_token( VALUE, $this->value ); 886 $this->optimise->shorthands(); 887 } 888 if ( ! $valid ) { 889 if ( $this->get_cfg( 'discard_invalid_properties' ) ) { 890 $this->log( 'Removed invalid property: ' . $this->property, 'Warning' ); 891 } 892 else { 893 $this->log( 'Invalid property in ' . strtoupper( $this->get_cfg( 'css_level' ) ) . ': ' . $this->property, 'Warning' ); 894 } 895 } 896 897 $this->property = ''; 898 $this->sub_value_arr = array(); 899 $this->value = ''; 900 } 901 if ( $string[ $i ] === '}' ) { 902 $this->explode_selectors(); 903 $this->_add_token( SEL_END, $this->selector ); 904 $this->status = 'is'; 905 $this->invalid_at = false; 906 $this->selector = ''; 907 if ( $this->next_selector_at ) { 908 $this->at = $this->css_close_media_section( $this->at ); 909 $this->at = $this->css_new_media_section( $this->at, $this->next_selector_at ); 910 $this->next_selector_at = ''; 911 } 912 } 913 } 914 elseif ( ! $pn ) { 915 $this->sub_value .= $string[ $i ]; 916 917 if ( ctype_space( $string[ $i ] ) ) { 918 $this->optimise->subvalue(); 919 if ( $this->sub_value != '' ) { 920 $this->sub_value_arr[] = $this->sub_value; 921 $this->sub_value = ''; 922 } 923 } 924 } 925 break; 926 927 /* Case in string */ 928 case 'instr': 929 $_str_char = $this->str_char[ count( $this->str_char ) - 1 ]; 930 $_cur_string = $this->cur_string[ count( $this->cur_string ) - 1 ]; 931 $_quoted_string = $this->quoted_string[ count( $this->quoted_string ) - 1 ]; 932 $temp_add = $string[ $i ]; 933 934 // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but 935 // parentheticals can be nested more than once. 936 if ( $_str_char === ")" && ( $string[ $i ] === "(" || $string[ $i ] === '"' || $string[ $i ] === '\'' ) && ! $this->escaped( $string, $i ) ) { 937 $this->cur_string[] = $string[ $i ]; 938 $this->str_char[] = $string[ $i ] === '(' ? ')' : $string[ $i ]; 939 $this->from[] = 'instr'; 940 $this->quoted_string[] = ( $_str_char === ')' && $string[ $i ] !== '(' && trim( $_cur_string ) === '(' ) ? $_quoted_string : ! ( $string[ $i ] === '(' ); 941 continue 2; 942 } 943 944 if ( $_str_char !== ")" && ( $string[ $i ] === "\n" || $string[ $i ] === "\r" ) && ! ( $string[ $i - 1 ] === '\\' && ! $this->escaped( $string, $i - 1 ) ) ) { 945 $temp_add = "\\A"; 946 $this->log( 'Fixed incorrect newline in string', 'Warning' ); 947 } 948 949 $_cur_string .= $temp_add; 950 951 if ( $string[ $i ] === $_str_char && ! $this->escaped( $string, $i ) ) { 952 $this->status = array_pop( $this->from ); 953 954 if ( ! preg_match( '|[' . implode( '', $this->data[ 'csstidy' ][ 'whitespace' ] ) . ']|uis', $_cur_string ) && $this->property !== 'content' ) { 955 if ( ! $_quoted_string ) { 956 if ( $_str_char !== ')' ) { 957 // Convert properties like 958 // font-family: 'Arial'; 959 // to 960 // font-family: Arial; 961 // or 962 // url("abc") 963 // to 964 // url(abc) 965 $_cur_string = substr( $_cur_string, 1, -1 ); 966 } 967 } 968 else { 969 $_quoted_string = false; 970 } 971 } 972 973 array_pop( $this->cur_string ); 974 array_pop( $this->quoted_string ); 975 array_pop( $this->str_char ); 976 977 if ( $_str_char === ')' ) { 978 $_cur_string = '(' . trim( substr( $_cur_string, 1, -1 ) ) . ')'; 979 } 980 981 if ( $this->status === 'iv' ) { 982 if ( ! $_quoted_string ) { 983 if ( strpos( $_cur_string, ',' ) !== false ) // we can on only remove space next to ',' 984 { 985 $_cur_string = implode( ',', array_map( 'trim', explode( ',', $_cur_string ) ) ); 986 } 987 // and multiple spaces (too expensive) 988 if ( strpos( $_cur_string, ' ' ) !== false ) { 989 $_cur_string = preg_replace( ",\s+,", ' ', $_cur_string ); 990 } 991 } 992 $this->sub_value .= $_cur_string; 993 } 994 elseif ( $this->status === 'is' ) { 995 $this->selector .= $_cur_string; 996 } 997 elseif ( $this->status === 'instr' ) { 998 $this->cur_string[ count( $this->cur_string ) - 1 ] .= $_cur_string; 999 } 1000 } 1001 else { 1002 $this->cur_string[ count( $this->cur_string ) - 1 ] = $_cur_string; 1003 } 1004 break; 1005 1006 /* Case in-comment */ 1007 case 'ic': 1008 if ( $string[ $i ] === '*' && $string[ $i + 1 ] === '/' ) { 1009 $this->status = array_pop( $this->from ); 1010 $i++; 1011 if ( strlen( $cur_comment ) > 1 and strncmp( $cur_comment, '!', 1 ) === 0 ) { 1012 $this->_add_token( IMPORTANT_COMMENT, $cur_comment ); 1013 $this->css_add_important_comment( $cur_comment ); 1014 } 1015 else { 1016 $this->_add_token( COMMENT, $cur_comment ); 1017 } 1018 $cur_comment = ''; 1019 } 1020 else { 1021 $cur_comment .= $string[ $i ]; 1022 } 1023 break; 1024 } 1025 } 1026 1027 $this->optimise->postparse(); 1028 1029 $this->print->_reset(); 1030 1031 @setlocale( LC_ALL, $old ); // Set locale back to original setting 1032 1033 return ! ( empty( $this->css ) && empty( $this->import ) && empty( $this->charset ) && empty( $this->tokens ) && empty( $this->namespace ) ); 1034 } 1035 1036 1037 /** 1038 * format() in font-face needs quoted values for somes browser (FF at least) 1039 * 1040 * @param $value 1041 * 1042 * @return string 1043 */ 1044 public function quote_font_format( $value ) { 1045 if ( strncmp( $value, 'format', 6 ) == 0 ) { 1046 $p = strpos( $value, ')', 7 ); 1047 $end = substr( $value, $p ); 1048 $format_strings = $this->parse_string_list( substr( $value, 7, $p - 7 ) ); 1049 if ( ! $format_strings ) { 1050 $value = ''; 1051 } 1052 else { 1053 $value = 'format('; 1054 1055 foreach ( $format_strings as $format_string ) { 1056 $value .= '"' . str_replace( '"', '\\"', $format_string ) . '",'; 1057 } 1058 1059 $value = substr( $value, 0, -1 ) . $end; 1060 } 1061 } 1062 return $value; 1063 } 1064 1065 /** 1066 * Explodes selectors 1067 * @access private 1068 * @version 1.0 1069 */ 1070 public function explode_selectors() { 1071 // Explode multiple selectors 1072 if ( $this->get_cfg( 'merge_selectors' ) === 1 ) { 1073 $new_sels = array(); 1074 $lastpos = 0; 1075 $this->sel_separate[] = strlen( $this->selector ); 1076 foreach ( $this->sel_separate as $num => $pos ) { 1077 if ( $num == count( $this->sel_separate ) - 1 ) { 1078 $pos += 1; 1079 } 1080 1081 $new_sels[] = substr( $this->selector, $lastpos, $pos - $lastpos - 1 ); 1082 $lastpos = $pos; 1083 } 1084 1085 if ( count( $new_sels ) > 1 ) { 1086 foreach ( $new_sels as $selector ) { 1087 if ( isset( $this->css[ $this->at ][ $this->selector ] ) ) { 1088 $this->merge_css_blocks( $this->at, $selector, $this->css[ $this->at ][ $this->selector ] ); 1089 } 1090 } 1091 unset( $this->css[ $this->at ][ $this->selector ] ); 1092 } 1093 } 1094 $this->sel_separate = array(); 1095 } 1096 1097 /** 1098 * Checks if a character is escaped (and returns true if it is) 1099 * 1100 * @param string $string 1101 * @param integer $pos 1102 * 1103 * @access public 1104 * @return bool 1105 * @version 1.02 1106 */ 1107 static function escaped( &$string, $pos ) { 1108 return ! ( @( $string[ $pos - 1 ] !== '\\' ) || csstidy::escaped( $string, $pos - 1 ) ); 1109 } 1110 1111 1112 /** 1113 * Add an important comment to the css code 1114 * (one we want to keep) 1115 * 1116 * @param $comment 1117 */ 1118 public function css_add_important_comment( $comment ) { 1119 if ( $this->get_cfg( 'preserve_css' ) || trim( $comment ) == '' ) { 1120 return; 1121 } 1122 if ( ! isset( $this->css[ '!' ] ) ) { 1123 $this->css[ '!' ] = ''; 1124 } 1125 else { 1126 $this->css[ '!' ] .= "\n"; 1127 } 1128 $this->css[ '!' ] .= $comment; 1129 } 1130 1131 /** 1132 * Adds a property with value to the existing CSS code 1133 * 1134 * @param string $media 1135 * @param string $selector 1136 * @param string $property 1137 * @param string $new_val 1138 * 1139 * @access private 1140 * @version 1.2 1141 */ 1142 public function css_add_property( $media, $selector, $property, $new_val ) { 1143 if ( $this->get_cfg( 'preserve_css' ) || trim( $new_val ) == '' ) { 1144 return; 1145 } 1146 1147 $this->added = true; 1148 if ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) { 1149 if ( ( $this->is_important( $this->css[ $media ][ $selector ][ $property ] ) && $this->is_important( $new_val ) ) || ! $this->is_important( $this->css[ $media ][ $selector ][ $property ] ) ) { 1150 $this->css[ $media ][ $selector ][ $property ] = trim( $new_val ); 1151 } 1152 } 1153 else { 1154 $this->css[ $media ][ $selector ][ $property ] = trim( $new_val ); 1155 } 1156 } 1157 1158 /** 1159 * Check if a current media section is the continuation of the last one 1160 * if not inc the name of the media section to avoid a merging 1161 * 1162 * @param int|string $media 1163 * 1164 * @return int|string 1165 */ 1166 public function css_check_last_media_section_or_inc( $media ) { 1167 // are we starting? 1168 if ( ! $this->css || ! is_array( $this->css ) || empty( $this->css ) ) { 1169 return $media; 1170 } 1171 1172 // if the last @media is the same as this 1173 // keep it 1174 end( $this->css ); 1175 $at = key( $this->css ); 1176 if ( $at == $media ) { 1177 return $media; 1178 } 1179 1180 // else inc the section in the array 1181 while ( isset( $this->css[ $media ] ) ) 1182 if ( is_numeric( $media ) ) { 1183 $media++; 1184 } 1185 else { 1186 $media .= ' '; 1187 } 1188 return $media; 1189 } 1190 1191 /** 1192 * Start a new media section. 1193 * Check if the media is not already known, 1194 * else rename it with extra spaces 1195 * to avoid merging 1196 * 1197 * @param string $current_media 1198 * @param string $media 1199 * @param bool $at_root 1200 * 1201 * @return string 1202 */ 1203 public function css_new_media_section( $current_media, $new_media, $at_root = false ) { 1204 if ( $this->get_cfg( 'preserve_css' ) ) { 1205 return $new_media; 1206 } 1207 1208 // if we already are in a media and CSS level is 3, manage nested medias 1209 if ( $current_media 1210 && ! $at_root 1211 // numeric $current_media means DEFAULT_AT or inc 1212 && ! is_numeric( $current_media ) 1213 && strncmp( $this->get_cfg( 'css_level' ), 'CSS3', 4 ) == 0 ) { 1214 1215 $new_media = rtrim( $current_media ) . "{" . rtrim( $new_media ); 1216 } 1217 1218 return $this->css_check_last_media_section_or_inc( $new_media ); 1219 } 1220 1221 /** 1222 * Close a media section 1223 * Find the parent media we were in before or the root 1224 * 1225 * @param $current_media 1226 * 1227 * @return string 1228 */ 1229 public function css_close_media_section( $current_media ) { 1230 if ( $this->get_cfg( 'preserve_css' ) ) { 1231 return ''; 1232 } 1233 1234 if ( strpos( $current_media, '{' ) !== false ) { 1235 $current_media = explode( '{', $current_media ); 1236 array_pop( $current_media ); 1237 $current_media = implode( '{', $current_media ); 1238 return $current_media; 1239 } 1240 1241 return ''; 1242 } 1243 1244 /** 1245 * Start a new selector. 1246 * If already referenced in this media section, 1247 * rename it with extra space to avoid merging 1248 * except if merging is required, 1249 * or last selector is the same (merge siblings) 1250 * 1251 * never merge @font-face 1252 * 1253 * @param string $media 1254 * @param string $selector 1255 * 1256 * @return string 1257 */ 1258 public function css_new_selector( $media, $selector ) { 1259 if ( $this->get_cfg( 'preserve_css' ) ) { 1260 return $selector; 1261 } 1262 $selector = trim( $selector ); 1263 if ( strncmp( $selector, '@font-face', 10 ) != 0 ) { 1264 if ( $this->settings[ 'merge_selectors' ] != false ) { 1265 return $selector; 1266 } 1267 1268 if ( ! $this->css || ! isset( $this->css[ $media ] ) || ! $this->css[ $media ] ) { 1269 return $selector; 1270 } 1271 1272 // if last is the same, keep it 1273 end( $this->css[ $media ] ); 1274 $sel = key( $this->css[ $media ] ); 1275 if ( $sel == $selector ) { 1276 return $selector; 1277 } 1278 } 1279 1280 while ( isset( $this->css[ $media ][ $selector ] ) ) 1281 $selector .= ' '; 1282 return $selector; 1283 } 1284 1285 /** 1286 * Start a new propertie. 1287 * If already references in this selector, 1288 * rename it with extra space to avoid override 1289 * 1290 * @param string $media 1291 * @param string $selector 1292 * @param string $property 1293 * 1294 * @return string 1295 */ 1296 public function css_new_property( $media, $selector, $property ) { 1297 if ( $this->get_cfg( 'preserve_css' ) ) { 1298 return $property; 1299 } 1300 if ( ! $this->css || ! isset( $this->css[ $media ][ $selector ] ) || ! $this->css[ $media ][ $selector ] ) { 1301 return $property; 1302 } 1303 1304 while ( isset( $this->css[ $media ][ $selector ][ $property ] ) ) 1305 $property .= ' '; 1306 1307 return $property; 1308 } 1309 1310 /** 1311 * Adds CSS to an existing media/selector 1312 * 1313 * @param string $media 1314 * @param string $selector 1315 * @param array $css_add 1316 * 1317 * @access private 1318 * @version 1.1 1319 */ 1320 public function merge_css_blocks( $media, $selector, $css_add ) { 1321 foreach ( $css_add as $property => $value ) { 1322 $this->css_add_property( $media, $selector, $property, $value, false ); 1323 } 1324 } 1325 1326 /** 1327 * Checks if $value is !important. 1328 * 1329 * @param string $value 1330 * 1331 * @return bool 1332 * @access public 1333 * @version 1.0 1334 */ 1335 public function is_important( &$value ) { 1336 return ( 1337 strpos( $value, '!' ) !== false // quick test 1338 AND ! strcasecmp( substr( str_replace( $this->data[ 'csstidy' ][ 'whitespace' ], '', $value ), -10, 10 ), '!important' ) ); 1339 } 1340 1341 /** 1342 * Returns a value without !important 1343 * 1344 * @param string $value 1345 * 1346 * @return string 1347 * @access public 1348 * @version 1.0 1349 */ 1350 public function gvw_important( $value ) { 1351 if ( $this->is_important( $value ) ) { 1352 $value = trim( $value ); 1353 $value = substr( $value, 0, -9 ); 1354 $value = trim( $value ); 1355 $value = substr( $value, 0, -1 ); 1356 $value = trim( $value ); 1357 return $value; 1358 } 1359 return $value; 1360 } 1361 1362 /** 1363 * Checks if the next word in a string from pos is a CSS property 1364 * 1365 * @param string $istring 1366 * @param integer $pos 1367 * 1368 * @return bool 1369 * @access private 1370 * @version 1.2 1371 */ 1372 public function property_is_next( $istring, $pos ) { 1373 $all_properties = &$this->data[ 'csstidy' ][ 'all_properties' ]; 1374 $istring = substr( $istring, $pos, strlen( $istring ) - $pos ); 1375 $pos = strpos( $istring, ':' ); 1376 if ( $pos === false ) { 1377 return false; 1378 } 1379 $istring = strtolower( trim( substr( $istring, 0, $pos ) ) ); 1380 if ( isset( $all_properties[ $istring ] ) ) { 1381 $this->log( 'Added semicolon to the end of declaration', 'Warning' ); 1382 return true; 1383 } 1384 return false; 1385 } 1386 1387 /** 1388 * Checks if a property is valid 1389 * 1390 * @param string $property 1391 * 1392 * @return bool 1393 * @access public 1394 * @version 1.0 1395 */ 1396 public function property_is_valid( $property ) { 1397 if ( in_array( trim( $property ), $this->data[ 'csstidy' ][ 'multiple_properties' ] ) ) { 1398 $property = trim( $property ); 1399 } 1400 $all_properties = &$this->data[ 'csstidy' ][ 'all_properties' ]; 1401 return ( isset( $all_properties[ $property ] ) && strpos( $all_properties[ $property ], strtoupper( $this->get_cfg( 'css_level' ) ) ) !== false ); 1402 } 1403 1404 /** 1405 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property) 1406 * and returns a list of the strings. Converts things like: 1407 * 1408 * format(abc) => format("abc") 1409 * format(abc def) => format("abc","def") 1410 * format(abc "def") => format("abc","def") 1411 * format(abc, def, ghi) => format("abc","def","ghi") 1412 * format("abc",'def') => format("abc","def") 1413 * format("abc, def, ghi") => format("abc, def, ghi") 1414 * 1415 * @param string 1416 * 1417 * @return array 1418 */ 1419 public function parse_string_list( $value ) { 1420 $value = trim( $value ); 1421 1422 // Case: empty 1423 if ( ! $value ) { 1424 return array(); 1425 } 1426 1427 $strings = array(); 1428 1429 $in_str = false; 1430 $current_string = ''; 1431 1432 for ( $i = 0, $_len = strlen( $value ); $i < $_len; $i++ ) { 1433 if ( ( $value[ $i ] === ',' || $value[ $i ] === ' ' ) && $in_str === true ) { 1434 $in_str = false; 1435 $strings[] = $current_string; 1436 $current_string = ''; 1437 } 1438 elseif ( $value[ $i ] === '"' || $value[ $i ] === "'" ) { 1439 if ( $in_str === $value[ $i ] ) { 1440 $strings[] = $current_string; 1441 $in_str = false; 1442 $current_string = ''; 1443 continue; 1444 } 1445 elseif ( ! $in_str ) { 1446 $in_str = $value[ $i ]; 1447 } 1448 } 1449 else { 1450 if ( $in_str ) { 1451 $current_string .= $value[ $i ]; 1452 } 1453 else { 1454 if ( ! preg_match( "/[\s,]/", $value[ $i ] ) ) { 1455 $in_str = true; 1456 $current_string = $value[ $i ]; 1457 } 1458 } 1459 } 1460 } 1461 1462 if ( $current_string ) { 1463 $strings[] = $current_string; 1464 } 1465 1466 return $strings; 1467 } 1468 } 293 $this->settings['sort_selectors'] = 0; 294 /* is dangeroues to be used: CSS is broken sometimes */ 295 $this->settings['merge_selectors'] = 0; 296 /* preserve or not browser hacks */ 297 298 /* Useful to produce a rtl css from a ltr one (or the opposite) */ 299 $this->settings['reverse_left_and_right'] = 0; 300 301 $this->settings['discard_invalid_selectors'] = false; 302 $this->settings['discard_invalid_properties'] = false; 303 $this->settings['css_level'] = 'CSS3.0'; 304 $this->settings['preserve_css'] = false; 305 $this->settings['timestamp'] = false; 306 $this->settings['template'] = ''; // say that propertie exist 307 $this->set_cfg('template','default'); // call load_template 308 $this->optimise = new csstidy_optimise($this); 309 310 $this->tokens_list = & $this->data['csstidy']['tokens']; 311 } 312 313 /** 314 * Get the value of a setting. 315 * @param string $setting 316 * @access public 317 * @return mixed 318 * @version 1.0 319 */ 320 public function get_cfg($setting) { 321 if (isset($this->settings[$setting])) { 322 return $this->settings[$setting]; 323 } 324 return false; 325 } 326 327 /** 328 * Load a template 329 * @param string $template used by set_cfg to load a template via a configuration setting 330 * @access private 331 * @version 1.4 332 */ 333 public function _load_template($template) { 334 switch ($template) { 335 case 'default': 336 $this->load_template('default'); 337 break; 338 339 case 'highest': 340 $this->load_template('highest_compression'); 341 break; 342 343 case 'high': 344 $this->load_template('high_compression'); 345 break; 346 347 case 'low': 348 $this->load_template('low_compression'); 349 break; 350 351 default: 352 $this->load_template($template); 353 break; 354 } 355 } 356 357 /** 358 * Set the value of a setting. 359 * @param string $setting 360 * @param mixed $value 361 * @access public 362 * @return bool 363 * @version 1.0 364 */ 365 public function set_cfg($setting, $value=null) { 366 if (is_array($setting) && $value === null) { 367 foreach ($setting as $setprop => $setval) { 368 $this->settings[$setprop] = $setval; 369 } 370 if (array_key_exists('template', $setting)) { 371 $this->_load_template($this->settings['template']); 372 } 373 return true; 374 } elseif (isset($this->settings[$setting]) && $value !== '') { 375 $this->settings[$setting] = $value; 376 if ($setting === 'template') { 377 $this->_load_template($this->settings['template']); 378 } 379 return true; 380 } 381 return false; 382 } 383 384 /** 385 * Adds a token to $this->tokens 386 * @param mixed $type 387 * @param string $data 388 * @param bool $do add a token even if preserve_css is off 389 * @access private 390 * @version 1.0 391 */ 392 public function _add_token($type, $data, $do = false) { 393 if ($this->get_cfg('preserve_css') || $do) { 394 // nested @... : if opening a new part we just closed, remove the previous closing instead of adding opening 395 if ($type === AT_START 396 and count($this->tokens) 397 and $last = end($this->tokens) 398 and $last[0] === AT_END 399 and $last[1] === trim($data)) { 400 array_pop($this->tokens); 401 } 402 else { 403 $this->tokens[] = array($type, ($type == COMMENT or $type == IMPORTANT_COMMENT) ? $data : trim($data)); 404 } 405 } 406 } 407 408 /** 409 * Add a message to the message log 410 * @param string $message 411 * @param string $type 412 * @param integer $line 413 * @access private 414 * @version 1.0 415 */ 416 public function log($message, $type, $line = -1) { 417 if ($line === -1) { 418 $line = $this->line; 419 } 420 $line = intval($line); 421 $add = array('m' => $message, 't' => $type); 422 if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) { 423 $this->log[$line][] = $add; 424 } 425 } 426 427 /** 428 * Parse unicode notations and find a replacement character 429 * @param string $string 430 * @param integer $i 431 * @access private 432 * @return string 433 * @version 1.2 434 */ 435 public function _unicode(&$string, &$i) { 436 ++$i; 437 $add = ''; 438 $replaced = false; 439 440 while ($i < strlen($string) && (ctype_xdigit($string[$i]) || ctype_space($string[$i])) && strlen($add) < 6) { 441 $add .= $string[$i]; 442 443 if (ctype_space($string[$i])) { 444 break; 445 } 446 $i++; 447 } 448 449 if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) { 450 $this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information'); 451 $add = chr(hexdec($add)); 452 $replaced = true; 453 } else { 454 $add = trim('\\' . $add); 455 } 456 457 if (@ctype_xdigit($string[$i + 1]) && ctype_space($string[$i]) 458 && !$replaced || !ctype_space($string[$i])) { 459 $i--; 460 } 461 462 if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string[$i + 1]) !== false) { 463 return $add; 464 } 465 466 if ($add === '\\') { 467 $this->log('Removed unnecessary backslash', 'Information'); 468 } 469 return ''; 470 } 471 472 /** 473 * Write formatted output to a file 474 * @param string $filename 475 * @param string $doctype when printing formatted, is a shorthand for the document type 476 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet 477 * @param string $title when printing formatted, is the title to be added in the head of the document 478 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output 479 * @access public 480 * @version 1.4 481 */ 482 public function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') { 483 $this->write($filename, true); 484 } 485 486 /** 487 * Write plain output to a file 488 * @param string $filename 489 * @param bool $formatted whether to print formatted or not 490 * @param string $doctype when printing formatted, is a shorthand for the document type 491 * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet 492 * @param string $title when printing formatted, is the title to be added in the head of the document 493 * @param string $lang when printing formatted, gives a two-letter language code to be added to the output 494 * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates) 495 * @access public 496 * @version 1.4 497 */ 498 public function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) { 499 $filename .= ( $formatted) ? '.xhtml' : '.css'; 500 501 if (!is_dir('temp')) { 502 $madedir = mkdir('temp'); 503 if (!$madedir) { 504 print 'Could not make directory "temp" in ' . dirname(__FILE__); 505 exit; 506 } 507 } 508 $handle = fopen('temp/' . $filename, 'w'); 509 if ($handle) { 510 if (!$formatted) { 511 fwrite($handle, $this->print->plain()); 512 } else { 513 fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code)); 514 } 515 } 516 fclose($handle); 517 } 518 519 /** 520 * Loads a new template 521 * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default" 522 * @param bool $from_file uses $content as filename if true 523 * @access public 524 * @version 1.1 525 * @see http://csstidy.sourceforge.net/templates.php 526 */ 527 public function load_template($content, $from_file=true) { 528 $predefined_templates = & $this->data['csstidy']['predefined_templates']; 529 if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') { 530 $this->template = $predefined_templates[$content]; 531 return; 532 } 533 534 535 if ($from_file) { 536 $content = strip_tags(file_get_contents($content), '<span>'); 537 } 538 $content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n) 539 $template = explode('|', $content); 540 541 for ($i = 0; $i < count($template); $i++) { 542 $this->template[$i] = $template[$i]; 543 } 544 } 545 546 /** 547 * Starts parsing from URL 548 * @param string $url 549 * @access public 550 * @version 1.0 551 */ 552 public function parse_from_url($url) { 553 return $this->parse(@file_get_contents($url)); 554 } 555 556 /** 557 * Checks if there is a token at the current position 558 * @param string $string 559 * @param integer $i 560 * @access public 561 * @version 1.11 562 */ 563 public function is_token(&$string, $i) { 564 return (strpos($this->tokens_list, $string[$i]) !== false && !$this->escaped($string, $i)); 565 } 566 567 /** 568 * Parses CSS in $string. The code is saved as array in $this->css 569 * @param string $string the CSS code 570 * @access public 571 * @return bool 572 * @version 1.1 573 */ 574 public function parse($string) { 575 // Temporarily set locale to en_US in order to handle floats properly 576 $old = @setlocale(LC_ALL, 0); 577 @setlocale(LC_ALL, 'C'); 578 579 // PHP bug? Settings need to be refreshed in PHP4 580 $this->print = new csstidy_print($this); 581 $this->optimise = new csstidy_optimise($this); 582 583 $all_properties = & $this->data['csstidy']['all_properties']; 584 $at_rules = & $this->data['csstidy']['at_rules']; 585 $quoted_string_properties = & $this->data['csstidy']['quoted_string_properties']; 586 587 $this->css = array(); 588 $this->print->input_css = $string; 589 $string = str_replace("\r\n", "\n", $string) . ' '; 590 $cur_comment = ''; 591 $cur_at = ''; 592 593 for ($i = 0, $size = strlen($string); $i < $size; $i++) { 594 if ($string[$i] === "\n" || $string[$i] === "\r") { 595 ++$this->line; 596 } 597 598 switch ($this->status) { 599 /* Case in at-block */ 600 case 'at': 601 if ($this->is_token($string, $i)) { 602 if ($string[$i] === '/' && @$string[$i + 1] === '*') { 603 $this->status = 'ic'; 604 ++$i; 605 $this->from[] = 'at'; 606 } elseif ($string[$i] === '{') { 607 $this->status = 'is'; 608 $this->at = $this->css_new_media_section($this->at, $cur_at); 609 $this->_add_token(AT_START, $this->at); 610 } elseif ($string[$i] === ',') { 611 $cur_at = trim($cur_at) . ','; 612 } elseif ($string[$i] === '\\') { 613 $cur_at .= $this->_unicode($string, $i); 614 } 615 // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5) 616 elseif (in_array($string[$i], array('(', ')', ':', '.', '/'))) { 617 $cur_at .= $string[$i]; 618 } 619 } else { 620 $lastpos = strlen($cur_at) - 1; 621 if (!( (ctype_space($cur_at[$lastpos]) || $this->is_token($cur_at, $lastpos) && $cur_at[$lastpos] === ',') && ctype_space($string[$i]))) { 622 $cur_at .= $string[$i]; 623 } 624 } 625 break; 626 627 /* Case in-selector */ 628 case 'is': 629 if ($this->is_token($string, $i)) { 630 if ($string[$i] === '/' && @$string[$i + 1] === '*' && trim($this->selector) == '') { 631 $this->status = 'ic'; 632 ++$i; 633 $this->from[] = 'is'; 634 } elseif ($string[$i] === '@' && trim($this->selector) == '') { 635 // Check for at-rule 636 $this->invalid_at = true; 637 foreach ($at_rules as $name => $type) { 638 if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) { 639 ($type === 'at') ? $cur_at = '@' . $name : $this->selector = '@' . $name; 640 if ($type === 'atis') { 641 $this->next_selector_at = ($this->next_selector_at?$this->next_selector_at:($this->at?$this->at:DEFAULT_AT)); 642 $this->at = $this->css_new_media_section($this->at, ' ', true); 643 $type = 'is'; 644 } 645 $this->status = $type; 646 $i += strlen($name); 647 $this->invalid_at = false; 648 break; 649 } 650 } 651 652 if ($this->invalid_at) { 653 $this->selector = '@'; 654 $invalid_at_name = ''; 655 for ($j = $i + 1; $j < $size; ++$j) { 656 if (!ctype_alpha($string[$j])) { 657 break; 658 } 659 $invalid_at_name .= $string[$j]; 660 } 661 $this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning'); 662 } 663 } elseif (($string[$i] === '"' || $string[$i] === "'")) { 664 $this->cur_string[] = $string[$i]; 665 $this->status = 'instr'; 666 $this->str_char[] = $string[$i]; 667 $this->from[] = 'is'; 668 /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */ 669 $this->quoted_string[] = ($string[$i - 1] === '=' ); 670 } elseif ($this->invalid_at && $string[$i] === ';') { 671 $this->invalid_at = false; 672 $this->status = 'is'; 673 if ($this->next_selector_at) { 674 $this->at = $this->css_close_media_section($this->at); 675 $this->at = $this->css_new_media_section($this->at, $this->next_selector_at); 676 $this->next_selector_at = ''; 677 } 678 } elseif ($string[$i] === '{') { 679 $this->status = 'ip'; 680 if ($this->at == '') { 681 $this->at = $this->css_new_media_section($this->at, DEFAULT_AT); 682 } 683 $this->selector = $this->css_new_selector($this->at,$this->selector); 684 $this->_add_token(SEL_START, $this->selector); 685 $this->added = false; 686 } elseif ($string[$i] === '}') { 687 $this->_add_token(AT_END, $this->at); 688 $this->at = $this->css_close_media_section($this->at); 689 $this->selector = ''; 690 $this->sel_separate = array(); 691 } elseif ($string[$i] === ',') { 692 $this->selector = trim($this->selector) . ','; 693 $this->sel_separate[] = strlen($this->selector); 694 } elseif ($string[$i] === '\\') { 695 $this->selector .= $this->_unicode($string, $i); 696 } elseif ($string[$i] === '*' && @in_array($string[$i + 1], array('.', '#', '[', ':')) && ($i==0 OR $string[$i - 1]!=='/')) { 697 // remove unnecessary universal selector, FS#147, but not comment in selector 698 } else { 699 $this->selector .= $string[$i]; 700 } 701 } else { 702 $lastpos = strlen($this->selector) - 1; 703 if ($lastpos == -1 || !( (ctype_space($this->selector[$lastpos]) || $this->is_token($this->selector, $lastpos) && $this->selector[$lastpos] === ',') && ctype_space($string[$i]))) { 704 $this->selector .= $string[$i]; 705 } 706 } 707 break; 708 709 /* Case in-property */ 710 case 'ip': 711 if ($this->is_token($string, $i)) { 712 if (($string[$i] === ':' || $string[$i] === '=') && $this->property != '') { 713 $this->status = 'iv'; 714 if (!$this->get_cfg('discard_invalid_properties') || $this->property_is_valid($this->property)) { 715 $this->property = $this->css_new_property($this->at,$this->selector,$this->property); 716 $this->_add_token(PROPERTY, $this->property); 717 } 718 } elseif ($string[$i] === '/' && @$string[$i + 1] === '*' && $this->property == '') { 719 $this->status = 'ic'; 720 ++$i; 721 $this->from[] = 'ip'; 722 } elseif ($string[$i] === '}') { 723 $this->explode_selectors(); 724 $this->status = 'is'; 725 $this->invalid_at = false; 726 $this->_add_token(SEL_END, $this->selector); 727 $this->selector = ''; 728 $this->property = ''; 729 if ($this->next_selector_at) { 730 $this->at = $this->css_close_media_section($this->at); 731 $this->at = $this->css_new_media_section($this->at, $this->next_selector_at); 732 $this->next_selector_at = ''; 733 } 734 } elseif ($string[$i] === ';') { 735 $this->property = ''; 736 } elseif ($string[$i] === '\\') { 737 $this->property .= $this->_unicode($string, $i); 738 } 739 // else this is dumb IE a hack, keep it 740 // including // 741 elseif (($this->property === '' && !ctype_space($string[$i])) 742 || ($this->property === '/' || $string[$i] === '/')) { 743 $this->property .= $string[$i]; 744 } 745 } elseif (!ctype_space($string[$i])) { 746 $this->property .= $string[$i]; 747 } 748 break; 749 750 /* Case in-value */ 751 case 'iv': 752 $pn = (($string[$i] === "\n" || $string[$i] === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1); 753 if ($this->is_token($string, $i) || $pn) { 754 if ($string[$i] === '/' && @$string[$i + 1] === '*') { 755 $this->status = 'ic'; 756 ++$i; 757 $this->from[] = 'iv'; 758 } elseif (($string[$i] === '"' || $string[$i] === "'" || $string[$i] === '(')) { 759 $this->cur_string[] = $string[$i]; 760 $this->str_char[] = ($string[$i] === '(') ? ')' : $string[$i]; 761 $this->status = 'instr'; 762 $this->from[] = 'iv'; 763 $this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties); 764 } elseif ($string[$i] === ',') { 765 $this->sub_value = trim($this->sub_value) . ','; 766 } elseif ($string[$i] === '\\') { 767 $this->sub_value .= $this->_unicode($string, $i); 768 } elseif ($string[$i] === ';' || $pn) { 769 if ($this->selector[0] === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') { 770 /* Add quotes to charset, import, namespace */ 771 $this->sub_value_arr[] = trim($this->sub_value); 772 773 $this->status = 'is'; 774 775 switch ($this->selector) { 776 case '@charset': $this->charset = '"'.$this->sub_value_arr[0].'"'; 777 break; 778 case '@namespace': $this->namespace = implode(' ', $this->sub_value_arr); 779 break; 780 case '@import': $this->import[] = implode(' ', $this->sub_value_arr); 781 break; 782 } 783 784 $this->sub_value_arr = array(); 785 $this->sub_value = ''; 786 $this->selector = ''; 787 $this->sel_separate = array(); 788 } else { 789 $this->status = 'ip'; 790 } 791 } elseif ($string[$i] !== '}') { 792 $this->sub_value .= $string[$i]; 793 } 794 if (($string[$i] === '}' || $string[$i] === ';' || $pn) && !empty($this->selector)) { 795 if ($this->at == '') { 796 $this->at = $this->css_new_media_section($this->at,DEFAULT_AT); 797 } 798 799 // case settings 800 if ($this->get_cfg('lowercase_s')) { 801 $this->selector = strtolower($this->selector); 802 } 803 $this->property = strtolower($this->property); 804 805 $this->optimise->subvalue(); 806 if ($this->sub_value != '') { 807 $this->sub_value_arr[] = $this->sub_value; 808 $this->sub_value = ''; 809 } 810 811 $this->value = ''; 812 while (count($this->sub_value_arr)) { 813 $sub = array_shift($this->sub_value_arr); 814 if (strstr($this->selector, 'font-face')) { 815 $sub = $this->quote_font_format($sub); 816 } 817 818 if ($sub != '') 819 $this->value .= ((!strlen($this->value) || substr($this->value,-1,1) === ',')?'':' ').$sub; 820 } 821 822 $this->optimise->value(); 823 824 $valid = $this->property_is_valid($this->property); 825 if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) { 826 $this->css_add_property($this->at, $this->selector, $this->property, $this->value); 827 $this->_add_token(VALUE, $this->value); 828 $this->optimise->shorthands(); 829 } 830 if (!$valid) { 831 if ($this->get_cfg('discard_invalid_properties')) { 832 $this->log('Removed invalid property: ' . $this->property, 'Warning'); 833 } else { 834 $this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning'); 835 } 836 } 837 838 $this->property = ''; 839 $this->sub_value_arr = array(); 840 $this->value = ''; 841 } 842 if ($string[$i] === '}') { 843 $this->explode_selectors(); 844 $this->_add_token(SEL_END, $this->selector); 845 $this->status = 'is'; 846 $this->invalid_at = false; 847 $this->selector = ''; 848 if ($this->next_selector_at) { 849 $this->at = $this->css_close_media_section($this->at); 850 $this->at = $this->css_new_media_section($this->at, $this->next_selector_at); 851 $this->next_selector_at = ''; 852 } 853 } 854 } elseif (!$pn) { 855 $this->sub_value .= $string[$i]; 856 857 if (ctype_space($string[$i])) { 858 $this->optimise->subvalue(); 859 if ($this->sub_value != '') { 860 $this->sub_value_arr[] = $this->sub_value; 861 $this->sub_value = ''; 862 } 863 } 864 } 865 break; 866 867 /* Case in string */ 868 case 'instr': 869 $_str_char = $this->str_char[count($this->str_char)-1]; 870 $_cur_string = $this->cur_string[count($this->cur_string)-1]; 871 $_quoted_string = $this->quoted_string[count($this->quoted_string)-1]; 872 $temp_add = $string[$i]; 873 874 // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but 875 // parentheticals can be nested more than once. 876 if ($_str_char === ")" && ($string[$i] === "(" || $string[$i] === '"' || $string[$i] === '\'') && !$this->escaped($string, $i)) { 877 $this->cur_string[] = $string[$i]; 878 $this->str_char[] = $string[$i] === '(' ? ')' : $string[$i]; 879 $this->from[] = 'instr'; 880 $this->quoted_string[] = ($_str_char === ')' && $string[$i] !== '(' && trim($_cur_string)==='(')?$_quoted_string:!($string[$i] === '('); 881 continue 2; 882 } 883 884 if ($_str_char !== ")" && ($string[$i] === "\n" || $string[$i] === "\r") && !($string[$i - 1] === '\\' && !$this->escaped($string, $i - 1))) { 885 $temp_add = "\\A"; 886 $this->log('Fixed incorrect newline in string', 'Warning'); 887 } 888 889 $_cur_string .= $temp_add; 890 891 if ($string[$i] === $_str_char && !$this->escaped($string, $i)) { 892 $this->status = array_pop($this->from); 893 894 if (!preg_match('|[' . implode('', $this->data['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') { 895 if (!$_quoted_string) { 896 if ($_str_char !== ')') { 897 // Convert properties like 898 // font-family: 'Arial'; 899 // to 900 // font-family: Arial; 901 // or 902 // url("abc") 903 // to 904 // url(abc) 905 $_cur_string = substr($_cur_string, 1, -1); 906 } 907 } else { 908 $_quoted_string = false; 909 } 910 } 911 912 array_pop($this->cur_string); 913 array_pop($this->quoted_string); 914 array_pop($this->str_char); 915 916 if ($_str_char === ')') { 917 $_cur_string = '(' . trim(substr($_cur_string, 1, -1)) . ')'; 918 } 919 920 if ($this->status === 'iv') { 921 if (!$_quoted_string) { 922 if (strpos($_cur_string,',') !== false) 923 // we can on only remove space next to ',' 924 $_cur_string = implode(',', array_map('trim', explode(',',$_cur_string))); 925 // and multiple spaces (too expensive) 926 if (strpos($_cur_string, ' ') !== false) 927 $_cur_string = preg_replace(",\s+,", ' ', $_cur_string); 928 } 929 $this->sub_value .= $_cur_string; 930 } elseif ($this->status === 'is') { 931 $this->selector .= $_cur_string; 932 } elseif ($this->status === 'instr') { 933 $this->cur_string[count($this->cur_string)-1] .= $_cur_string; 934 } 935 } else { 936 $this->cur_string[count($this->cur_string)-1] = $_cur_string; 937 } 938 break; 939 940 /* Case in-comment */ 941 case 'ic': 942 if ($string[$i] === '*' && $string[$i + 1] === '/') { 943 $this->status = array_pop($this->from); 944 $i++; 945 if (strlen($cur_comment) > 1 and strncmp($cur_comment, '!', 1) === 0) { 946 $this->_add_token(IMPORTANT_COMMENT, $cur_comment); 947 $this->css_add_important_comment($cur_comment); 948 } 949 else { 950 $this->_add_token(COMMENT, $cur_comment); 951 } 952 $cur_comment = ''; 953 } else { 954 $cur_comment .= $string[$i]; 955 } 956 break; 957 } 958 } 959 960 $this->optimise->postparse(); 961 962 $this->print->_reset(); 963 964 @setlocale(LC_ALL, $old); // Set locale back to original setting 965 966 return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace)); 967 } 968 969 970 /** 971 * format() in font-face needs quoted values for somes browser (FF at least) 972 * 973 * @param $value 974 * @return string 975 */ 976 public function quote_font_format($value) { 977 if (strncmp($value,'format',6) == 0) { 978 $p = strpos($value,')',7); 979 $end = substr($value,$p); 980 $format_strings = $this->parse_string_list(substr($value, 7, $p-7)); 981 if (!$format_strings) { 982 $value = ''; 983 } else { 984 $value = 'format('; 985 986 foreach ($format_strings as $format_string) { 987 $value .= '"' . str_replace('"', '\\"', $format_string) . '",'; 988 } 989 990 $value = substr($value, 0, -1) . $end; 991 } 992 } 993 return $value; 994 } 995 996 /** 997 * Explodes selectors 998 * @access private 999 * @version 1.0 1000 */ 1001 public function explode_selectors() { 1002 // Explode multiple selectors 1003 if ($this->get_cfg('merge_selectors') === 1) { 1004 $new_sels = array(); 1005 $lastpos = 0; 1006 $this->sel_separate[] = strlen($this->selector); 1007 foreach ($this->sel_separate as $num => $pos) { 1008 if ($num == count($this->sel_separate) - 1) { 1009 $pos += 1; 1010 } 1011 1012 $new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1); 1013 $lastpos = $pos; 1014 } 1015 1016 if (count($new_sels) > 1) { 1017 foreach ($new_sels as $selector) { 1018 if (isset($this->css[$this->at][$this->selector])) { 1019 $this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]); 1020 } 1021 } 1022 unset($this->css[$this->at][$this->selector]); 1023 } 1024 } 1025 $this->sel_separate = array(); 1026 } 1027 1028 /** 1029 * Checks if a character is escaped (and returns true if it is) 1030 * @param string $string 1031 * @param integer $pos 1032 * @access public 1033 * @return bool 1034 * @version 1.02 1035 */ 1036 static function escaped(&$string, $pos) { 1037 return!(@($string[$pos - 1] !== '\\') || csstidy::escaped($string, $pos - 1)); 1038 } 1039 1040 1041 /** 1042 * Add an important comment to the css code 1043 * (one we want to keep) 1044 * @param $comment 1045 */ 1046 public function css_add_important_comment($comment) { 1047 if ($this->get_cfg('preserve_css') || trim($comment) == '') { 1048 return; 1049 } 1050 if (!isset($this->css['!'])) { 1051 $this->css['!'] = ''; 1052 } 1053 else { 1054 $this->css['!'] .= "\n"; 1055 } 1056 $this->css['!'] .= $comment; 1057 } 1058 1059 /** 1060 * Adds a property with value to the existing CSS code 1061 * @param string $media 1062 * @param string $selector 1063 * @param string $property 1064 * @param string $new_val 1065 * @access private 1066 * @version 1.2 1067 */ 1068 public function css_add_property($media, $selector, $property, $new_val) { 1069 if ($this->get_cfg('preserve_css') || trim($new_val) == '') { 1070 return; 1071 } 1072 1073 $this->added = true; 1074 if (isset($this->css[$media][$selector][$property])) { 1075 if (($this->is_important($this->css[$media][$selector][$property]) && $this->is_important($new_val)) || !$this->is_important($this->css[$media][$selector][$property])) { 1076 $this->css[$media][$selector][$property] = trim($new_val); 1077 } 1078 } else { 1079 $this->css[$media][$selector][$property] = trim($new_val); 1080 } 1081 } 1082 1083 /** 1084 * Check if a current media section is the continuation of the last one 1085 * if not inc the name of the media section to avoid a merging 1086 * 1087 * @param int|string $media 1088 * @return int|string 1089 */ 1090 public function css_check_last_media_section_or_inc($media) { 1091 // are we starting? 1092 if (!$this->css || !is_array($this->css) || empty($this->css)) { 1093 return $media; 1094 } 1095 1096 // if the last @media is the same as this 1097 // keep it 1098 end($this->css); 1099 $at = key($this->css); 1100 if ($at == $media) { 1101 return $media; 1102 } 1103 1104 // else inc the section in the array 1105 while (isset($this->css[$media])) 1106 if (is_numeric($media)) 1107 $media++; 1108 else 1109 $media .= ' '; 1110 return $media; 1111 } 1112 1113 /** 1114 * Start a new media section. 1115 * Check if the media is not already known, 1116 * else rename it with extra spaces 1117 * to avoid merging 1118 * 1119 * @param string $current_media 1120 * @param string $media 1121 * @param bool $at_root 1122 * @return string 1123 */ 1124 public function css_new_media_section($current_media, $new_media, $at_root = false) { 1125 if ($this->get_cfg('preserve_css')) { 1126 return $new_media; 1127 } 1128 1129 // if we already are in a media and CSS level is 3, manage nested medias 1130 if ($current_media 1131 && !$at_root 1132 // numeric $current_media means DEFAULT_AT or inc 1133 && !is_numeric($current_media) 1134 && strncmp($this->get_cfg('css_level'), 'CSS3', 4) == 0) { 1135 1136 $new_media = rtrim($current_media) . "{" . rtrim($new_media); 1137 } 1138 1139 return $this->css_check_last_media_section_or_inc($new_media); 1140 } 1141 1142 /** 1143 * Close a media section 1144 * Find the parent media we were in before or the root 1145 * @param $current_media 1146 * @return string 1147 */ 1148 public function css_close_media_section($current_media) { 1149 if ($this->get_cfg('preserve_css')) { 1150 return ''; 1151 } 1152 1153 if (strpos($current_media, '{') !== false) { 1154 $current_media = explode('{', $current_media); 1155 array_pop($current_media); 1156 $current_media = implode('{', $current_media); 1157 return $current_media; 1158 } 1159 1160 return ''; 1161 } 1162 1163 /** 1164 * Start a new selector. 1165 * If already referenced in this media section, 1166 * rename it with extra space to avoid merging 1167 * except if merging is required, 1168 * or last selector is the same (merge siblings) 1169 * 1170 * never merge @font-face 1171 * 1172 * @param string $media 1173 * @param string $selector 1174 * @return string 1175 */ 1176 public function css_new_selector($media,$selector) { 1177 if ($this->get_cfg('preserve_css')) { 1178 return $selector; 1179 } 1180 $selector = trim($selector); 1181 if (strncmp($selector,'@font-face',10)!=0) { 1182 if ($this->settings['merge_selectors'] != false) 1183 return $selector; 1184 1185 if (!$this->css || !isset($this->css[$media]) || !$this->css[$media]) 1186 return $selector; 1187 1188 // if last is the same, keep it 1189 end($this->css[$media]); 1190 $sel = key($this->css[$media]); 1191 if ($sel == $selector) { 1192 return $selector; 1193 } 1194 } 1195 1196 while (isset($this->css[$media][$selector])) 1197 $selector .= ' '; 1198 return $selector; 1199 } 1200 1201 /** 1202 * Start a new propertie. 1203 * If already references in this selector, 1204 * rename it with extra space to avoid override 1205 * 1206 * @param string $media 1207 * @param string $selector 1208 * @param string $property 1209 * @return string 1210 */ 1211 public function css_new_property($media, $selector, $property) { 1212 if ($this->get_cfg('preserve_css')) { 1213 return $property; 1214 } 1215 if (!$this->css || !isset($this->css[$media][$selector]) || !$this->css[$media][$selector]) 1216 return $property; 1217 1218 while (isset($this->css[$media][$selector][$property])) 1219 $property .= ' '; 1220 1221 return $property; 1222 } 1223 1224 /** 1225 * Adds CSS to an existing media/selector 1226 * @param string $media 1227 * @param string $selector 1228 * @param array $css_add 1229 * @access private 1230 * @version 1.1 1231 */ 1232 public function merge_css_blocks($media, $selector, $css_add) { 1233 foreach ($css_add as $property => $value) { 1234 $this->css_add_property($media, $selector, $property, $value, false); 1235 } 1236 } 1237 1238 /** 1239 * Checks if $value is !important. 1240 * @param string $value 1241 * @return bool 1242 * @access public 1243 * @version 1.0 1244 */ 1245 public function is_important(&$value) { 1246 return ( 1247 strpos($value, '!') !== false // quick test 1248 AND !strcasecmp(substr(str_replace($this->data['csstidy']['whitespace'], '', $value), -10, 10), '!important')); 1249 } 1250 1251 /** 1252 * Returns a value without !important 1253 * @param string $value 1254 * @return string 1255 * @access public 1256 * @version 1.0 1257 */ 1258 public function gvw_important($value) { 1259 if ($this->is_important($value)) { 1260 $value = trim($value); 1261 $value = substr($value, 0, -9); 1262 $value = trim($value); 1263 $value = substr($value, 0, -1); 1264 $value = trim($value); 1265 return $value; 1266 } 1267 return $value; 1268 } 1269 1270 /** 1271 * Checks if the next word in a string from pos is a CSS property 1272 * @param string $istring 1273 * @param integer $pos 1274 * @return bool 1275 * @access private 1276 * @version 1.2 1277 */ 1278 public function property_is_next($istring, $pos) { 1279 $all_properties = & $this->data['csstidy']['all_properties']; 1280 $istring = substr($istring, $pos, strlen($istring) - $pos); 1281 $pos = strpos($istring, ':'); 1282 if ($pos === false) { 1283 return false; 1284 } 1285 $istring = strtolower(trim(substr($istring, 0, $pos))); 1286 if (isset($all_properties[$istring])) { 1287 $this->log('Added semicolon to the end of declaration', 'Warning'); 1288 return true; 1289 } 1290 return false; 1291 } 1292 1293 /** 1294 * Checks if a property is valid 1295 * @param string $property 1296 * @return bool 1297 * @access public 1298 * @version 1.0 1299 */ 1300 public function property_is_valid($property) { 1301 if (strpos($property, '--') === 0) { 1302 $property = "--custom"; 1303 } 1304 elseif (in_array(trim($property), $this->data['csstidy']['multiple_properties'])) { 1305 $property = trim($property); 1306 } 1307 $all_properties = & $this->data['csstidy']['all_properties']; 1308 return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false ); 1309 } 1310 1311 /** 1312 * Accepts a list of strings (e.g., the argument to format() in a @font-face src property) 1313 * and returns a list of the strings. Converts things like: 1314 * 1315 * format(abc) => format("abc") 1316 * format(abc def) => format("abc","def") 1317 * format(abc "def") => format("abc","def") 1318 * format(abc, def, ghi) => format("abc","def","ghi") 1319 * format("abc",'def') => format("abc","def") 1320 * format("abc, def, ghi") => format("abc, def, ghi") 1321 * 1322 * @param string 1323 * @return array 1324 */ 1325 public function parse_string_list($value) { 1326 $value = trim($value); 1327 1328 // Case: empty 1329 if (!$value) return array(); 1330 1331 $strings = array(); 1332 1333 $in_str = false; 1334 $current_string = ''; 1335 1336 for ($i = 0, $_len = strlen($value); $i < $_len; $i++) { 1337 if (($value[$i] === ',' || $value[$i] === ' ') && $in_str === true) { 1338 $in_str = false; 1339 $strings[] = $current_string; 1340 $current_string = ''; 1341 } elseif ($value[$i] === '"' || $value[$i] === "'") { 1342 if ($in_str === $value[$i]) { 1343 $strings[] = $current_string; 1344 $in_str = false; 1345 $current_string = ''; 1346 continue; 1347 } elseif (!$in_str) { 1348 $in_str = $value[$i]; 1349 } 1350 } else { 1351 if ($in_str) { 1352 $current_string .= $value[$i]; 1353 } else { 1354 if (!preg_match("/[\s,]/", $value[$i])) { 1355 $in_str = true; 1356 $current_string = $value[$i]; 1357 } 1358 } 1359 } 1360 } 1361 1362 if ($current_string) { 1363 $strings[] = $current_string; 1364 } 1365 1366 return $strings; 1367 } 1368 } -
opt-out-for-google-analytics/trunk/lib/csstidy/class.csstidy_optimise.php
r2518394 r2787042 1 1 <?php 2 2 3 // If this file is called directly, abort. 4 defined( 'WPINC' ) || die; 5 6 /** 7 * CSSTidy - CSS Parser and Optimiser 8 * 9 * CSS Optimising Class 10 * This class optimises CSS data generated by csstidy. 11 * 12 * Copyright 2005, 2006, 2007 Florian Schmitz 13 * 14 * This file is part of CSSTidy. 15 * 16 * CSSTidy is free software; you can redistribute it and/or modify 17 * it under the terms of the GNU Lesser General Public License as published by 18 * the Free Software Foundation; either version 2.1 of the License, or 19 * (at your option) any later version. 20 * 21 * CSSTidy is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 * GNU Lesser General Public License for more details. 25 * 26 * You should have received a copy of the GNU Lesser General Public License 27 * along with this program. If not, see <http://www.gnu.org/licenses/>. 28 * 29 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 30 * @package csstidy 31 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 32 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 33 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 34 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 35 */ 36 37 /** 38 * CSS Optimising Class 39 * 40 * This class optimises CSS data generated by csstidy. 41 * 42 * @package csstidy 43 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 44 * @version 1.0 45 */ 46 class csstidy_optimise { 47 48 /** 49 * csstidy object 50 * @var object 51 */ 52 public $parser; 53 54 /** 55 * Constructor 56 * 57 * @param array $css contains the class csstidy 58 * 59 * @access private 60 * @version 1.0 61 */ 62 public function __construct( $css ) { 63 $this->parser = $css; 64 $this->css = &$css->css; 65 $this->sub_value = &$css->sub_value; 66 $this->at = &$css->at; 67 $this->selector = &$css->selector; 68 $this->property = &$css->property; 69 $this->value = &$css->value; 70 } 71 72 /** 73 * Optimises $css after parsing 74 * @access public 75 * @version 1.0 76 */ 77 public function postparse() { 78 79 if ( $this->parser->get_cfg( 'reverse_left_and_right' ) > 0 ) { 80 81 foreach ( $this->css as $medium => $selectors ) { 82 if ( is_array( $selectors ) ) { 83 foreach ( $selectors as $selector => $properties ) { 84 $this->css[ $medium ][ $selector ] = $this->reverse_left_and_right( $this->css[ $medium ][ $selector ] ); 3 /** 4 * CSSTidy - CSS Parser and Optimiser 5 * 6 * CSS Optimising Class 7 * This class optimises CSS data generated by csstidy. 8 * 9 * Copyright 2005, 2006, 2007 Florian Schmitz 10 * 11 * This file is part of CSSTidy. 12 * 13 * CSSTidy is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU Lesser General Public License as published by 15 * the Free Software Foundation; either version 2.1 of the License, or 16 * (at your option) any later version. 17 * 18 * CSSTidy is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU Lesser General Public License for more details. 22 * 23 * You should have received a copy of the GNU Lesser General Public License 24 * along with this program. If not, see <http://www.gnu.org/licenses/>. 25 * 26 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 27 * @package csstidy 28 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 29 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 30 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 31 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 32 */ 33 34 /** 35 * CSS Optimising Class 36 * 37 * This class optimises CSS data generated by csstidy. 38 * 39 * @package csstidy 40 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 41 * @version 1.0 42 */ 43 class csstidy_optimise { 44 45 /** 46 * csstidy object 47 * @var object 48 */ 49 public $parser; 50 51 /** 52 * Constructor 53 * @param array $css contains the class csstidy 54 * @access private 55 * @version 1.0 56 */ 57 public function __construct($css) { 58 $this->parser = $css; 59 $this->css = & $css->css; 60 $this->sub_value = & $css->sub_value; 61 $this->at = & $css->at; 62 $this->selector = & $css->selector; 63 $this->property = & $css->property; 64 $this->value = & $css->value; 65 } 66 67 /** 68 * Optimises $css after parsing 69 * @access public 70 * @version 1.0 71 */ 72 public function postparse() { 73 74 if ($this->parser->get_cfg('reverse_left_and_right') > 0) { 75 76 foreach ($this->css as $medium => $selectors) { 77 if (is_array($selectors)) { 78 foreach ($selectors as $selector => $properties) { 79 $this->css[$medium][$selector] = $this->reverse_left_and_right($this->css[$medium][$selector]); 80 } 81 } 82 } 83 84 } 85 86 if ($this->parser->get_cfg('preserve_css')) { 87 return; 88 } 89 90 if ((int)$this->parser->get_cfg('merge_selectors') === 2) { 91 foreach ($this->css as $medium => $value) { 92 if (is_array($value)) { 93 $this->merge_selectors($this->css[$medium]); 94 } 95 } 96 } 97 98 if ($this->parser->get_cfg('discard_invalid_selectors')) { 99 foreach ($this->css as $medium => $value) { 100 if (is_array($value)) { 101 $this->discard_invalid_selectors($this->css[$medium]); 102 } 103 } 104 } 105 106 if ($this->parser->get_cfg('optimise_shorthands') > 0) { 107 foreach ($this->css as $medium => $value) { 108 if (is_array($value)) { 109 foreach ($value as $selector => $value1) { 110 $this->css[$medium][$selector] = $this->merge_4value_shorthands($this->css[$medium][$selector]); 111 $this->css[$medium][$selector] = $this->merge_4value_radius_shorthands($this->css[$medium][$selector]); 112 113 if ($this->parser->get_cfg('optimise_shorthands') < 2) { 114 continue; 115 } 116 117 $this->css[$medium][$selector] = $this->merge_font($this->css[$medium][$selector]); 118 119 if ($this->parser->get_cfg('optimise_shorthands') < 3) { 120 continue; 121 } 122 123 $this->css[$medium][$selector] = $this->merge_bg($this->css[$medium][$selector]); 124 if (empty($this->css[$medium][$selector])) { 125 unset($this->css[$medium][$selector]); 126 } 127 } 128 } 129 } 130 } 131 } 132 133 /** 134 * Optimises values 135 * @access public 136 * @version 1.0 137 */ 138 public function value() { 139 $shorthands = & $this->parser->data['csstidy']['shorthands']; 140 141 // optimise shorthand properties 142 if (isset($shorthands[$this->property])) { 143 $temp = $this->shorthand($this->value); // FIXME - move 144 if ($temp != $this->value) { 145 $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information'); 146 } 147 $this->value = $temp; 148 } 149 150 // Remove whitespace at ! important 151 if ($this->value != $this->compress_important($this->value)) { 152 $this->parser->log('Optimised !important', 'Information'); 153 } 154 } 155 156 /** 157 * Optimises shorthands 158 * @access public 159 * @version 1.0 160 */ 161 public function shorthands() { 162 $shorthands = & $this->parser->data['csstidy']['shorthands']; 163 164 if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) { 165 return; 166 } 167 168 if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) { 169 $this->css[$this->at][$this->selector]['font']=''; 170 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_short_font($this->value)); 171 } 172 if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) { 173 $this->css[$this->at][$this->selector]['background']=''; 174 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_short_bg($this->value)); 175 } 176 if (isset($shorthands[$this->property])) { 177 $this->parser->merge_css_blocks($this->at, $this->selector, $this->dissolve_4value_shorthands($this->property, $this->value)); 178 if (is_array($shorthands[$this->property])) { 179 $this->css[$this->at][$this->selector][$this->property] = ''; 180 } 181 } 182 } 183 184 /** 185 * Optimises a sub-value 186 * @access public 187 * @version 1.0 188 */ 189 public function subvalue() { 190 $replace_colors = & $this->parser->data['csstidy']['replace_colors']; 191 192 $this->sub_value = trim($this->sub_value); 193 if ($this->sub_value == '') { // caution : '0' 194 return; 195 } 196 197 $important = ''; 198 if ($this->parser->is_important($this->sub_value)) { 199 $important = '!important'; 200 } 201 $this->sub_value = $this->parser->gvw_important($this->sub_value); 202 203 // Compress font-weight 204 if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) { 205 if ($this->sub_value === 'bold') { 206 $this->sub_value = '700'; 207 $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information'); 208 } elseif ($this->sub_value === 'normal') { 209 $this->sub_value = '400'; 210 $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information'); 211 } 212 } 213 214 $temp = $this->compress_numbers($this->sub_value); 215 if (strcasecmp($temp, $this->sub_value) !== 0) { 216 if (strlen($temp) > strlen($this->sub_value)) { 217 $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); 218 } else { 219 $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); 220 } 221 $this->sub_value = $temp; 222 } 223 if ($this->parser->get_cfg('compress_colors')) { 224 $temp = $this->cut_color($this->sub_value); 225 if ($temp !== $this->sub_value) { 226 if (isset($replace_colors[$this->sub_value])) { 227 $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning'); 228 } else { 229 $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information'); 230 } 231 $this->sub_value = $temp; 232 } 233 } 234 $this->sub_value .= $important; 235 } 236 237 /** 238 * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px 239 * @param string $value 240 * @access public 241 * @return string 242 * @version 1.0 243 */ 244 public function shorthand($value) { 245 $important = ''; 246 if ($this->parser->is_important($value)) { 247 $values = $this->parser->gvw_important($value); 248 $important = '!important'; 249 } 250 else 251 $values = $value; 252 253 $values = explode(' ', $values); 254 switch (count($values)) { 255 case 4: 256 if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) { 257 return $values[0] . $important; 258 } elseif ($values[1] == $values[3] && $values[0] == $values[2]) { 259 return $values[0] . ' ' . $values[1] . $important; 260 } elseif ($values[1] == $values[3]) { 261 return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important; 262 } 263 break; 264 265 case 3: 266 if ($values[0] == $values[1] && $values[0] == $values[2]) { 267 return $values[0] . $important; 268 } elseif ($values[0] == $values[2]) { 269 return $values[0] . ' ' . $values[1] . $important; 270 } 271 break; 272 273 case 2: 274 if ($values[0] == $values[1]) { 275 return $values[0] . $important; 276 } 277 break; 278 } 279 280 return $value; 281 } 282 283 /** 284 * Removes unnecessary whitespace in ! important 285 * @param string $string 286 * @return string 287 * @access public 288 * @version 1.1 289 */ 290 public function compress_important(&$string) { 291 if ($this->parser->is_important($string)) { 292 $important = $this->parser->get_cfg('space_before_important') ? ' !important' : '!important'; 293 $string = $this->parser->gvw_important($string) . $important; 294 } 295 return $string; 296 } 297 298 /** 299 * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. 300 * @param string $color 301 * @return string 302 * @version 1.1 303 */ 304 public function cut_color($color) { 305 $replace_colors = & $this->parser->data['csstidy']['replace_colors']; 306 307 // if it's a string, don't touch ! 308 if (strncmp($color, "'", 1) == 0 || strncmp($color, '"', 1) == 0) 309 return $color; 310 311 /* expressions complexes de type gradient */ 312 if (strpos($color, '(') !== false && strncmp($color, 'rgb(' ,4) != 0) { 313 // on ne touche pas aux couleurs dans les expression ms, c'est trop sensible 314 if (stripos($color, 'progid:') !== false) 315 return $color; 316 preg_match_all(",rgb\([^)]+\),i", $color, $matches, PREG_SET_ORDER); 317 if (count($matches)) { 318 foreach ($matches as $m) { 319 $color = str_replace($m[0], $this->cut_color($m[0]), $color); 320 } 321 } 322 preg_match_all(",#[0-9a-f]{6}(?=[^0-9a-f]),i", $color, $matches, PREG_SET_ORDER); 323 if (count($matches)) { 324 foreach ($matches as $m) { 325 $color = str_replace($m[0],$this->cut_color($m[0]), $color); 326 } 327 } 328 return $color; 329 } 330 331 // rgb(0,0,0) -> #000000 (or #000 in this case later) 332 if (strncasecmp($color, 'rgb(', 4)==0) { 333 $color_tmp = substr($color, 4, strlen($color) - 5); 334 $color_tmp = explode(',', $color_tmp); 335 for ($i = 0; $i < count($color_tmp); $i++) { 336 $color_tmp[$i] = trim($color_tmp[$i]); 337 if (substr($color_tmp[$i], -1) === '%') { 338 $color_tmp[$i] = round((255 * $color_tmp[$i]) / 100); 339 } 340 if ($color_tmp[$i] > 255) 341 $color_tmp[$i] = 255; 342 } 343 $color = '#'; 344 for ($i = 0; $i < 3; $i++) { 345 if ($color_tmp[$i] < 16) { 346 $color .= '0' . dechex($color_tmp[$i]); 347 } else { 348 $color .= dechex($color_tmp[$i]); 349 } 350 } 351 } 352 353 // Fix bad color names 354 if (isset($replace_colors[strtolower($color)])) { 355 $color = $replace_colors[strtolower($color)]; 356 } 357 358 // #aabbcc -> #abc 359 if (strlen($color) == 7) { 360 $color_temp = strtolower($color); 361 if ($color_temp[0] === '#' && $color_temp[1] == $color_temp[2] && $color_temp[3] == $color_temp[4] && $color_temp[5] == $color_temp[6]) { 362 $color = '#' . $color[1] . $color[3] . $color[5]; 363 } 364 } 365 366 switch (strtolower($color)) { 367 /* color name -> hex code */ 368 case 'black': return '#000'; 369 case 'fuchsia': return '#f0f'; 370 case 'white': return '#fff'; 371 case 'yellow': return '#ff0'; 372 373 /* hex code -> color name */ 374 case '#800000': return 'maroon'; 375 case '#ffa500': return 'orange'; 376 case '#808000': return 'olive'; 377 case '#800080': return 'purple'; 378 case '#008000': return 'green'; 379 case '#000080': return 'navy'; 380 case '#008080': return 'teal'; 381 case '#c0c0c0': return 'silver'; 382 case '#808080': return 'gray'; 383 case '#f00': return 'red'; 384 } 385 386 return $color; 387 } 388 389 /** 390 * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 ) 391 * @param string $subvalue 392 * @return string 393 * @version 1.2 394 */ 395 public function compress_numbers($subvalue) { 396 $unit_values = & $this->parser->data['csstidy']['unit_values']; 397 $color_values = & $this->parser->data['csstidy']['color_values']; 398 399 // for font:1em/1em sans-serif...; 400 if ($this->property === 'font') { 401 $temp = explode('/', $subvalue); 402 } else { 403 $temp = array($subvalue); 404 } 405 406 for ($l = 0; $l < count($temp); $l++) { 407 // if we are not dealing with a number at this point, do not optimise anything 408 $number = $this->AnalyseCssNumber($temp[$l]); 409 if ($number === false) { 410 return $subvalue; 411 } 412 413 // Fix bad colors 414 if (in_array($this->property, $color_values)) { 415 $temp[$l] = '#' . $temp[$l]; 416 continue; 417 } 418 419 if (abs($number[0]) > 0) { 420 if ($number[1] == '' && in_array($this->property, $unit_values, true)) { 421 $number[1] = 'px'; 422 } 423 } elseif ($number[1] != 's' && $number[1] != 'ms') { 424 $number[1] = ''; 85 425 } 86 } 87 } 88 89 } 90 91 if ( $this->parser->get_cfg( 'preserve_css' ) ) { 92 return; 93 } 94 95 if ( (int) $this->parser->get_cfg( 'merge_selectors' ) === 2 ) { 96 foreach ( $this->css as $medium => $value ) { 97 if ( is_array( $value ) ) { 98 $this->merge_selectors( $this->css[ $medium ] ); 99 } 100 } 101 } 102 103 if ( $this->parser->get_cfg( 'discard_invalid_selectors' ) ) { 104 foreach ( $this->css as $medium => $value ) { 105 if ( is_array( $value ) ) { 106 $this->discard_invalid_selectors( $this->css[ $medium ] ); 107 } 108 } 109 } 110 111 if ( $this->parser->get_cfg( 'optimise_shorthands' ) > 0 ) { 112 foreach ( $this->css as $medium => $value ) { 113 if ( is_array( $value ) ) { 114 foreach ( $value as $selector => $value1 ) { 115 $this->css[ $medium ][ $selector ] = $this->merge_4value_shorthands( $this->css[ $medium ][ $selector ] ); 116 $this->css[ $medium ][ $selector ] = $this->merge_4value_radius_shorthands( $this->css[ $medium ][ $selector ] ); 117 118 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 2 ) { 119 continue; 120 } 121 122 $this->css[ $medium ][ $selector ] = $this->merge_font( $this->css[ $medium ][ $selector ] ); 123 124 if ( $this->parser->get_cfg( 'optimise_shorthands' ) < 3 ) { 125 continue; 126 } 127 128 $this->css[ $medium ][ $selector ] = $this->merge_bg( $this->css[ $medium ][ $selector ] ); 129 if ( empty( $this->css[ $medium ][ $selector ] ) ) { 130 unset( $this->css[ $medium ][ $selector ] ); 131 } 132 } 133 } 134 } 135 } 136 } 137 138 /** 139 * Reverse left vs right in a list of properties/values 140 * 141 * @param array $array 142 * 143 * @return array 144 */ 145 public function reverse_left_and_right( $array ) { 146 $return = array(); 147 148 // change left <-> right in properties name and values 149 foreach ( $array as $propertie => $value ) { 150 151 if ( method_exists( $this, $m = 'reverse_left_and_right_' . str_replace( '-', '_', trim( $propertie ) ) ) ) { 152 $value = $this->$m( $value ); 153 } 154 155 // simple replacement for properties 156 $propertie = str_ireplace( array( 'left', 'right', "\x1" ), array( "\x1", 'left', 'right' ), $propertie ); 157 // be careful for values, not modifying protected or quoted valued 158 foreach ( array( 'left' => "\x1", 'right' => 'left', "\x1" => 'right' ) as $v => $r ) { 159 if ( strpos( $value, $v ) !== false ) { 160 // attraper les left et right separes du reste (pas au milieu d'un mot) 161 if ( in_array( $v, array( 'left', 'right' ) ) ) { 162 $value = preg_replace( ",\\b$v\\b,", "\x0", $value ); 163 } 164 else { 165 $value = str_replace( $v, "\x0", $value ); 166 } 167 $value = $this->explode_ws( "\x0", $value . ' ', true ); 168 $value = rtrim( implode( $r, $value ) ); 169 $value = str_replace( "\x0", $v, $value ); 170 } 171 } 172 $return[ $propertie ] = $value; 173 } 174 175 return $return; 176 } 177 178 /** 179 * Explodes a string as explode() does, however, not if $sep is escaped or within a string. 180 * 181 * @param string $sep seperator 182 * @param string $string 183 * @param bool $explode_in_parenthesis 184 * 185 * @return array 186 * @version 1.0 187 */ 188 public function explode_ws( $sep, $string, $explode_in_parenthesis = false ) { 189 $status = 'st'; 190 $to = ''; 191 192 $output = array( 193 0 => '', 194 ); 195 $num = 0; 196 for ( $i = 0, $len = strlen( $string ); $i < $len; $i++ ) { 197 switch ( $status ) { 198 case 'st': 199 if ( $string[ $i ] == $sep && ! $this->parser->escaped( $string, $i ) ) { 200 ++$num; 201 } 202 elseif ( $string[ $i ] === '"' || $string[ $i ] === '\'' || ( ! $explode_in_parenthesis && $string[ $i ] === '(' ) && ! $this->parser->escaped( $string, $i ) ) { 203 $status = 'str'; 204 $to = ( $string[ $i ] === '(' ) ? ')' : $string[ $i ]; 205 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ]; 206 } 207 else { 208 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ]; 209 } 210 break; 211 212 case 'str': 213 if ( $string[ $i ] == $to && ! $this->parser->escaped( $string, $i ) ) { 214 $status = 'st'; 215 } 216 ( isset( $output[ $num ] ) ) ? $output[ $num ] .= $string[ $i ] : $output[ $num ] = $string[ $i ]; 217 break; 218 } 219 } 220 221 return $output; 222 } 223 224 /** 225 * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} 226 * Very basic and has at least one bug. Hopefully there is a replacement soon. 227 * 228 * @param array $array 229 * 230 * @return array 231 * @access public 232 * @version 1.2 233 */ 234 public function merge_selectors( &$array ) { 235 $css = $array; 236 foreach ( $css as $key => $value ) { 237 if ( ! isset( $css[ $key ] ) ) { 238 continue; 239 } 240 $newsel = ''; 241 242 // Check if properties also exist in another selector 243 $keys = array(); 244 // PHP bug (?) without $css = $array; here 245 foreach ( $css as $selector => $vali ) { 246 if ( $selector == $key ) { 247 continue; 248 } 249 250 if ( $css[ $key ] === $vali ) { 251 $keys[] = $selector; 252 } 253 } 254 255 if ( ! empty( $keys ) ) { 256 $newsel = $key; 257 unset( $css[ $key ] ); 258 foreach ( $keys as $selector ) { 259 unset( $css[ $selector ] ); 260 $newsel .= ',' . $selector; 261 } 262 $css[ $newsel ] = $value; 263 } 264 } 265 $array = $css; 266 } 267 268 /** 269 * Removes invalid selectors and their corresponding rule-sets as 270 * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check 271 * and should be replaced by a full-blown parsing algorithm or 272 * regular expression 273 * @version 1.4 274 */ 275 public function discard_invalid_selectors( &$array ) { 276 $invalid = array( '+' => true, '~' => true, ',' => true, '>' => true ); 277 foreach ( $array as $selector => $decls ) { 278 $ok = true; 279 $selectors = array_map( 'trim', explode( ',', $selector ) ); 280 foreach ( $selectors as $s ) { 281 $simple_selectors = preg_split( '/\s*[+>~\s]\s*/', $s ); 282 foreach ( $simple_selectors as $ss ) { 283 if ( $ss === '' ) { 284 $ok = false; 285 } 286 // could also check $ss for internal structure, 287 // but that probably would be too slow 288 } 289 } 290 if ( ! $ok ) { 291 unset( $array[ $selector ] ); 292 } 293 } 294 } 295 296 /** 297 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() 298 * 299 * @param array $array 300 * @param array|null $shorthands 301 * 302 * @return array 303 * @version 1.2 304 * @see dissolve_4value_shorthands() 305 */ 306 public function merge_4value_shorthands( $array, $shorthands = null ) { 307 $return = $array; 308 if ( is_null( $shorthands ) ) { 309 $shorthands = &$this->parser->data[ 'csstidy' ][ 'shorthands' ]; 310 } 311 312 foreach ( $shorthands as $key => $value ) { 313 if ( $value !== 0 && isset( $array[ $value[ 0 ] ] ) && isset( $array[ $value[ 1 ] ] ) 314 && isset( $array[ $value[ 2 ] ] ) && isset( $array[ $value[ 3 ] ] ) ) { 315 $return[ $key ] = ''; 316 317 $important = ''; 318 for ( $i = 0; $i < 4; $i++ ) { 319 $val = $array[ $value[ $i ] ]; 320 if ( $this->parser->is_important( $val ) ) { 321 $important = '!important'; 322 $return[ $key ] .= $this->parser->gvw_important( $val ) . ' '; 323 } 324 else { 325 $return[ $key ] .= $val . ' '; 326 } 327 unset( $return[ $value[ $i ] ] ); 328 } 329 $return[ $key ] = $this->shorthand( trim( $return[ $key ] . $important ) ); 330 } 331 } 332 return $return; 333 } 334 335 /** 336 * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px 337 * 338 * @param string $value 339 * 340 * @access public 341 * @return string 342 * @version 1.0 343 */ 344 public function shorthand( $value ) { 345 $important = ''; 346 if ( $this->parser->is_important( $value ) ) { 347 $values = $this->parser->gvw_important( $value ); 348 $important = '!important'; 349 } 350 else { 351 $values = $value; 352 } 353 354 $values = explode( ' ', $values ); 355 switch ( count( $values ) ) { 356 case 4: 357 if ( $values[ 0 ] == $values[ 1 ] && $values[ 0 ] == $values[ 2 ] && $values[ 0 ] == $values[ 3 ] ) { 358 return $values[ 0 ] . $important; 359 } 360 elseif ( $values[ 1 ] == $values[ 3 ] && $values[ 0 ] == $values[ 2 ] ) { 361 return $values[ 0 ] . ' ' . $values[ 1 ] . $important; 362 } 363 elseif ( $values[ 1 ] == $values[ 3 ] ) { 364 return $values[ 0 ] . ' ' . $values[ 1 ] . ' ' . $values[ 2 ] . $important; 365 } 366 break; 367 368 case 3: 369 if ( $values[ 0 ] == $values[ 1 ] && $values[ 0 ] == $values[ 2 ] ) { 370 return $values[ 0 ] . $important; 371 } 372 elseif ( $values[ 0 ] == $values[ 2 ] ) { 373 return $values[ 0 ] . ' ' . $values[ 1 ] . $important; 374 } 375 break; 376 377 case 2: 378 if ( $values[ 0 ] == $values[ 1 ] ) { 379 return $values[ 0 ] . $important; 380 } 381 break; 382 } 383 384 return $value; 385 } 386 387 /** 388 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() 389 * 390 * @param array $array 391 * 392 * @return array 393 * @version 1.2 394 * @use merge_4value_shorthands() 395 * @see dissolve_4value_radius_shorthands() 396 */ 397 public function merge_4value_radius_shorthands( $array ) { 398 $return = $array; 399 $shorthands = &$this->parser->data[ 'csstidy' ][ 'radius_shorthands' ]; 400 401 foreach ( $shorthands as $key => $value ) { 402 if ( isset( $array[ $value[ 0 ] ] ) && isset( $array[ $value[ 1 ] ] ) 403 && isset( $array[ $value[ 2 ] ] ) && isset( $array[ $value[ 3 ] ] ) && $value !== 0 ) { 404 $return[ $key ] = ''; 405 $a = array(); 406 for ( $i = 0; $i < 4; $i++ ) { 407 $v = $this->explode_ws( ' ', trim( $array[ $value[ $i ] ] ) ); 408 $a[ 0 ][ $value[ $i ] ] = reset( $v ); 409 $a[ 1 ][ $value[ $i ] ] = end( $v ); 410 } 411 $r = array(); 412 $r[ 0 ] = $this->merge_4value_shorthands( $a[ 0 ], $shorthands ); 413 $r[ 1 ] = $this->merge_4value_shorthands( $a[ 1 ], $shorthands ); 414 415 if ( isset( $r[ 0 ][ $key ] ) and isset( $r[ 1 ][ $key ] ) ) { 416 $return[ $key ] = $r[ 0 ][ $key ]; 417 if ( $r[ 1 ][ $key ] !== $r[ 0 ][ $key ] ) { 418 $return[ $key ] .= ' / ' . $r[ 1 ][ $key ]; 419 } 420 for ( $i = 0; $i < 4; $i++ ) { 421 unset( $return[ $value[ $i ] ] ); 422 } 423 } 424 } 425 } 426 return $return; 427 } 428 429 /** 430 * Merges all fonts properties 431 * 432 * @param array $input_css 433 * 434 * @return array 435 * @version 1.3 436 * @see dissolve_short_font() 437 */ 438 public function merge_font( $input_css ) { 439 $font_prop_default = &$this->parser->data[ 'csstidy' ][ 'font_prop_default' ]; 440 $new_font_value = ''; 441 $important = ''; 442 // Skip if not font-family and font-size set 443 if ( isset( $input_css[ 'font-family' ] ) && isset( $input_css[ 'font-size' ] ) && $input_css[ 'font-family' ] != 'inherit' ) { 444 // fix several words in font-family - add quotes 445 if ( isset( $input_css[ 'font-family' ] ) ) { 446 $families = explode( ',', $input_css[ 'font-family' ] ); 447 $result_families = array(); 448 foreach ( $families as $family ) { 449 $family = trim( $family ); 450 $len = strlen( $family ); 451 if ( strpos( $family, ' ' ) && 452 ! ( ( $family[ 0 ] === '"' && $family[ $len - 1 ] === '"' ) || 453 ( $family[ 0 ] === "'" && $family[ $len - 1 ] === "'" ) ) ) { 454 $family = '"' . $family . '"'; 455 } 456 $result_families[] = $family; 457 } 458 $input_css[ 'font-family' ] = implode( ',', $result_families ); 459 } 460 foreach ( $font_prop_default as $font_property => $default_value ) { 461 462 // Skip if property does not exist 463 if ( ! isset( $input_css[ $font_property ] ) ) { 464 continue; 465 } 466 467 $cur_value = $input_css[ $font_property ]; 468 469 // Skip if default value is used 470 if ( $cur_value === $default_value ) { 471 continue; 472 } 473 474 // Remove !important 475 if ( $this->parser->is_important( $cur_value ) ) { 476 $important = '!important'; 477 $cur_value = $this->parser->gvw_important( $cur_value ); 478 } 479 480 $new_font_value .= $cur_value; 481 // Add delimiter 482 $new_font_value .= ( $font_property === 'font-size' && 483 isset( $input_css[ 'line-height' ] ) ) ? '/' : ' '; 484 } 485 486 $new_font_value = trim( $new_font_value ); 487 488 // Delete all font-properties 489 foreach ( $font_prop_default as $font_property => $default_value ) { 490 if ( $font_property !== 'font' || ! $new_font_value ) { 491 unset( $input_css[ $font_property ] ); 492 } 493 } 494 495 // Add new font property 496 if ( $new_font_value !== '' ) { 497 $input_css[ 'font' ] = $new_font_value . $important; 498 } 499 } 500 501 return $input_css; 502 } 503 504 /** 505 * Merges all background properties 506 * 507 * @param array $input_css 508 * 509 * @return array 510 * @version 1.0 511 * @see dissolve_short_bg() 512 * @todo full CSS 3 compliance 513 */ 514 public function merge_bg( $input_css ) { 515 $background_prop_default = &$this->parser->data[ 'csstidy' ][ 'background_prop_default' ]; 516 // Max number of background images. CSS3 not yet fully implemented 517 $number_of_values = @max( count( $this->explode_ws( ',', $input_css[ 'background-image' ] ) ), count( $this->explode_ws( ',', $input_css[ 'background-color' ] ) ), 1 ); 518 // Array with background images to check if BG image exists 519 $bg_img_array = @$this->explode_ws( ',', $this->parser->gvw_important( $input_css[ 'background-image' ] ) ); 520 $new_bg_value = ''; 521 $important = ''; 522 523 // if background properties is here and not empty, don't try anything 524 if ( isset( $input_css[ 'background' ] ) && $input_css[ 'background' ] ) { 525 return $input_css; 526 } 527 528 for ( $i = 0; $i < $number_of_values; $i++ ) { 529 foreach ( $background_prop_default as $bg_property => $default_value ) { 530 // Skip if property does not exist 531 if ( ! isset( $input_css[ $bg_property ] ) ) { 532 continue; 533 } 534 535 $cur_value = $input_css[ $bg_property ]; 536 // skip all optimisation if gradient() somewhere 537 if ( stripos( $cur_value, 'gradient(' ) !== false ) { 538 return $input_css; 539 } 540 541 // Skip some properties if there is no background image 542 if ( ( ! isset( $bg_img_array[ $i ] ) || $bg_img_array[ $i ] === 'none' ) 543 && ( $bg_property === 'background-size' || $bg_property === 'background-position' 544 || $bg_property === 'background-attachment' || $bg_property === 'background-repeat' ) ) { 545 continue; 546 } 547 548 // Remove !important 549 if ( $this->parser->is_important( $cur_value ) ) { 550 $important = ' !important'; 551 $cur_value = $this->parser->gvw_important( $cur_value ); 552 } 553 554 // Do not add default values 555 if ( $cur_value === $default_value ) { 556 continue; 557 } 558 559 $temp = $this->explode_ws( ',', $cur_value ); 560 561 if ( isset( $temp[ $i ] ) ) { 562 if ( $bg_property === 'background-size' ) { 563 $new_bg_value .= '(' . $temp[ $i ] . ') '; 564 } 565 else { 566 $new_bg_value .= $temp[ $i ] . ' '; 567 } 568 } 569 } 570 571 $new_bg_value = trim( $new_bg_value ); 572 if ( $i != $number_of_values - 1 ) { 573 $new_bg_value .= ','; 574 } 575 } 576 577 // Delete all background-properties 578 foreach ( $background_prop_default as $bg_property => $default_value ) { 579 unset( $input_css[ $bg_property ] ); 580 } 581 582 // Add new background property 583 if ( $new_bg_value !== '' ) { 584 $input_css[ 'background' ] = $new_bg_value . $important; 585 } 586 elseif ( isset ( $input_css[ 'background' ] ) ) { 587 $input_css[ 'background' ] = 'none'; 588 } 589 590 return $input_css; 591 } 592 593 /** 594 * Optimises values 595 * @access public 596 * @version 1.0 597 */ 598 public function value() { 599 $shorthands = &$this->parser->data[ 'csstidy' ][ 'shorthands' ]; 600 601 // optimise shorthand properties 602 if ( isset( $shorthands[ $this->property ] ) ) { 603 $temp = $this->shorthand( $this->value ); // FIXME - move 604 if ( $temp != $this->value ) { 605 $this->parser->log( 'Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information' ); 606 } 607 $this->value = $temp; 608 } 609 610 // Remove whitespace at ! important 611 if ( $this->value != $this->compress_important( $this->value ) ) { 612 $this->parser->log( 'Optimised !important', 'Information' ); 613 } 614 } 615 616 /** 617 * Removes unnecessary whitespace in ! important 618 * 619 * @param string $string 620 * 621 * @return string 622 * @access public 623 * @version 1.1 624 */ 625 public function compress_important( &$string ) { 626 if ( $this->parser->is_important( $string ) ) { 627 $important = $this->parser->get_cfg( 'space_before_important' ) ? ' !important' : '!important'; 628 $string = $this->parser->gvw_important( $string ) . $important; 629 } 630 return $string; 631 } 632 633 /** 634 * Optimises shorthands 635 * @access public 636 * @version 1.0 637 */ 638 public function shorthands() { 639 $shorthands = &$this->parser->data[ 'csstidy' ][ 'shorthands' ]; 640 641 if ( ! $this->parser->get_cfg( 'optimise_shorthands' ) || $this->parser->get_cfg( 'preserve_css' ) ) { 642 return; 643 } 644 645 if ( $this->property === 'font' && $this->parser->get_cfg( 'optimise_shorthands' ) > 1 ) { 646 $this->css[ $this->at ][ $this->selector ][ 'font' ] = ''; 647 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_font( $this->value ) ); 648 } 649 if ( $this->property === 'background' && $this->parser->get_cfg( 'optimise_shorthands' ) > 2 ) { 650 $this->css[ $this->at ][ $this->selector ][ 'background' ] = ''; 651 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_short_bg( $this->value ) ); 652 } 653 if ( isset( $shorthands[ $this->property ] ) ) { 654 $this->parser->merge_css_blocks( $this->at, $this->selector, $this->dissolve_4value_shorthands( $this->property, $this->value ) ); 655 if ( is_array( $shorthands[ $this->property ] ) ) { 656 $this->css[ $this->at ][ $this->selector ][ $this->property ] = ''; 657 } 658 } 659 } 660 661 /** 662 * Dissolve font property 663 * 664 * @param string $str_value 665 * 666 * @return array 667 * @version 1.3 668 * @see merge_font() 669 */ 670 public function dissolve_short_font( $str_value ) { 671 $font_prop_default = &$this->parser->data[ 'csstidy' ][ 'font_prop_default' ]; 672 $font_weight = array( 'normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900 ); 673 $font_variant = array( 'normal', 'small-caps' ); 674 $font_style = array( 'normal', 'italic', 'oblique' ); 675 $important = ''; 676 $return = array( 'font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null ); 677 678 if ( $this->parser->is_important( $str_value ) ) { 679 $important = '!important'; 680 $str_value = $this->parser->gvw_important( $str_value ); 681 } 682 683 $have[ 'style' ] = false; 684 $have[ 'variant' ] = false; 685 $have[ 'weight' ] = false; 686 $have[ 'size' ] = false; 687 // Detects if font-family consists of several words w/o quotes 688 $multiwords = false; 689 690 // Workaround with multiple font-family 691 $str_value = $this->explode_ws( ',', trim( $str_value ) ); 692 693 $str_value[ 0 ] = $this->explode_ws( ' ', trim( $str_value[ 0 ] ) ); 694 695 for ( $j = 0; $j < count( $str_value[ 0 ] ); $j++ ) { 696 if ( $have[ 'weight' ] === false && in_array( $str_value[ 0 ][ $j ], $font_weight ) ) { 697 $return[ 'font-weight' ] = $str_value[ 0 ][ $j ]; 698 $have[ 'weight' ] = true; 699 } 700 elseif ( $have[ 'variant' ] === false && in_array( $str_value[ 0 ][ $j ], $font_variant ) ) { 701 $return[ 'font-variant' ] = $str_value[ 0 ][ $j ]; 702 $have[ 'variant' ] = true; 703 } 704 elseif ( $have[ 'style' ] === false && in_array( $str_value[ 0 ][ $j ], $font_style ) ) { 705 $return[ 'font-style' ] = $str_value[ 0 ][ $j ]; 706 $have[ 'style' ] = true; 707 } 708 elseif ( $have[ 'size' ] === false && ( is_numeric( $str_value[ 0 ][ $j ][ 0 ] ) || $str_value[ 0 ][ $j ][ 0 ] === null || $str_value[ 0 ][ $j ][ 0 ] === '.' ) ) { 709 $size = $this->explode_ws( '/', trim( $str_value[ 0 ][ $j ] ) ); 710 $return[ 'font-size' ] = $size[ 0 ]; 711 if ( isset( $size[ 1 ] ) ) { 712 $return[ 'line-height' ] = $size[ 1 ]; 713 } 714 else { 715 $return[ 'line-height' ] = ''; // don't add 'normal' ! 716 } 717 $have[ 'size' ] = true; 718 } 719 else { 720 if ( isset( $return[ 'font-family' ] ) ) { 721 $return[ 'font-family' ] .= ' ' . $str_value[ 0 ][ $j ]; 722 $multiwords = true; 723 } 724 else { 725 $return[ 'font-family' ] = $str_value[ 0 ][ $j ]; 726 } 727 } 728 } 729 // add quotes if we have several qords in font-family 730 if ( $multiwords !== false ) { 731 $return[ 'font-family' ] = '"' . $return[ 'font-family' ] . '"'; 732 } 733 $i = 1; 734 while ( isset( $str_value[ $i ] ) ) { 735 $return[ 'font-family' ] .= ',' . trim( $str_value[ $i ] ); 736 $i++; 737 } 738 739 // Fix for 100 and more font-size 740 if ( $have[ 'size' ] === false && isset( $return[ 'font-weight' ] ) && 741 is_numeric( $return[ 'font-weight' ][ 0 ] ) ) { 742 $return[ 'font-size' ] = $return[ 'font-weight' ]; 743 unset( $return[ 'font-weight' ] ); 744 } 745 746 foreach ( $font_prop_default as $font_prop => $default_value ) { 747 if ( $return[ $font_prop ] !== null ) { 748 $return[ $font_prop ] = $return[ $font_prop ] . $important; 749 } 750 else { 751 $return[ $font_prop ] = $default_value . $important; 752 } 753 } 754 return $return; 755 } 756 757 /** 758 * Dissolve background property 759 * 760 * @param string $str_value 761 * 762 * @return array 763 * @version 1.0 764 * @see merge_bg() 765 * @todo full CSS 3 compliance 766 */ 767 public function dissolve_short_bg( $str_value ) { 768 // don't try to explose background gradient ! 769 if ( stripos( $str_value, 'gradient(' ) !== false ) { 770 return array( 'background' => $str_value ); 771 } 772 773 $background_prop_default = &$this->parser->data[ 'csstidy' ][ 'background_prop_default' ]; 774 $repeat = array( 'repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space' ); 775 $attachment = array( 'scroll', 'fixed', 'local' ); 776 $clip = array( 'border', 'padding' ); 777 $origin = array( 'border', 'padding', 'content' ); 778 $pos = array( 'top', 'center', 'bottom', 'left', 'right' ); 779 $important = ''; 780 $return = array( 'background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null ); 781 782 if ( $this->parser->is_important( $str_value ) ) { 783 $important = ' !important'; 784 $str_value = $this->parser->gvw_important( $str_value ); 785 } 786 787 $str_value = $this->explode_ws( ',', $str_value ); 788 for ( $i = 0; $i < count( $str_value ); $i++ ) { 789 $have[ 'clip' ] = false; 790 $have[ 'pos' ] = false; 791 $have[ 'color' ] = false; 792 $have[ 'bg' ] = false; 793 794 if ( is_array( $str_value[ $i ] ) ) { 795 $str_value[ $i ] = $str_value[ $i ][ 0 ]; 796 } 797 $str_value[ $i ] = $this->explode_ws( ' ', trim( $str_value[ $i ] ) ); 798 799 for ( $j = 0; $j < count( $str_value[ $i ] ); $j++ ) { 800 if ( $have[ 'bg' ] === false && ( substr( $str_value[ $i ][ $j ], 0, 4 ) === 'url(' || $str_value[ $i ][ $j ] === 'none' ) ) { 801 $return[ 'background-image' ] .= $str_value[ $i ][ $j ] . ','; 802 $have[ 'bg' ] = true; 803 } 804 elseif ( in_array( $str_value[ $i ][ $j ], $repeat, true ) ) { 805 $return[ 'background-repeat' ] .= $str_value[ $i ][ $j ] . ','; 806 } 807 elseif ( in_array( $str_value[ $i ][ $j ], $attachment, true ) ) { 808 $return[ 'background-attachment' ] .= $str_value[ $i ][ $j ] . ','; 809 } 810 elseif ( in_array( $str_value[ $i ][ $j ], $clip, true ) && ! $have[ 'clip' ] ) { 811 $return[ 'background-clip' ] .= $str_value[ $i ][ $j ] . ','; 812 $have[ 'clip' ] = true; 813 } 814 elseif ( in_array( $str_value[ $i ][ $j ], $origin, true ) ) { 815 $return[ 'background-origin' ] .= $str_value[ $i ][ $j ] . ','; 816 } 817 elseif ( $str_value[ $i ][ $j ][ 0 ] === '(' ) { 818 $return[ 'background-size' ] .= substr( $str_value[ $i ][ $j ], 1, -1 ) . ','; 819 } 820 elseif ( in_array( $str_value[ $i ][ $j ], $pos, true ) || is_numeric( $str_value[ $i ][ $j ][ 0 ] ) || $str_value[ $i ][ $j ][ 0 ] === null || $str_value[ $i ][ $j ][ 0 ] === '-' || $str_value[ $i ][ $j ][ 0 ] === '.' ) { 821 $return[ 'background-position' ] .= $str_value[ $i ][ $j ]; 822 if ( ! $have[ 'pos' ] ) { 823 $return[ 'background-position' ] .= ' '; 824 } 825 else { 826 $return[ 'background-position' ] .= ','; 827 } 828 $have[ 'pos' ] = true; 829 } 830 elseif ( ! $have[ 'color' ] ) { 831 $return[ 'background-color' ] .= $str_value[ $i ][ $j ] . ','; 832 $have[ 'color' ] = true; 833 } 834 } 835 } 836 837 foreach ( $background_prop_default as $bg_prop => $default_value ) { 838 if ( $return[ $bg_prop ] !== null ) { 839 $return[ $bg_prop ] = substr( $return[ $bg_prop ], 0, -1 ) . $important; 840 } 841 else { 842 $return[ $bg_prop ] = $default_value . $important; 843 } 844 } 845 return $return; 846 } 847 848 /** 849 * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... 850 * 851 * @param string $property 852 * @param string $value 853 * @param array|null $shorthands 854 * 855 * @return array 856 * @version 1.0 857 * @see merge_4value_shorthands() 858 */ 859 public function dissolve_4value_shorthands( $property, $value, $shorthands = null ) { 860 if ( is_null( $shorthands ) ) { 861 $shorthands = &$this->parser->data[ 'csstidy' ][ 'shorthands' ]; 862 } 863 if ( ! is_array( $shorthands[ $property ] ) ) { 864 $return[ $property ] = $value; 865 return $return; 866 } 867 868 $important = ''; 869 if ( $this->parser->is_important( $value ) ) { 870 $value = $this->parser->gvw_important( $value ); 871 $important = '!important'; 872 } 873 $values = explode( ' ', $value ); 874 875 876 $return = array(); 877 if ( count( $values ) == 4 ) { 878 for ( $i = 0; $i < 4; $i++ ) { 879 $return[ $shorthands[ $property ][ $i ] ] = $values[ $i ] . $important; 880 } 881 } 882 elseif ( count( $values ) == 3 ) { 883 $return[ $shorthands[ $property ][ 0 ] ] = $values[ 0 ] . $important; 884 $return[ $shorthands[ $property ][ 1 ] ] = $values[ 1 ] . $important; 885 $return[ $shorthands[ $property ][ 3 ] ] = $values[ 1 ] . $important; 886 $return[ $shorthands[ $property ][ 2 ] ] = $values[ 2 ] . $important; 887 } 888 elseif ( count( $values ) == 2 ) { 889 for ( $i = 0; $i < 4; $i++ ) { 890 $return[ $shorthands[ $property ][ $i ] ] = ( ( $i % 2 != 0 ) ) ? $values[ 1 ] . $important : $values[ 0 ] . $important; 891 } 892 } 893 else { 894 for ( $i = 0; $i < 4; $i++ ) { 895 $return[ $shorthands[ $property ][ $i ] ] = $values[ 0 ] . $important; 896 } 897 } 898 899 return $return; 900 } 901 902 /** 903 * Optimises a sub-value 904 * @access public 905 * @version 1.0 906 */ 907 public function subvalue() { 908 $replace_colors = &$this->parser->data[ 'csstidy' ][ 'replace_colors' ]; 909 910 $this->sub_value = trim( $this->sub_value ); 911 if ( $this->sub_value == '' ) { // caution : '0' 912 return; 913 } 914 915 $important = ''; 916 if ( $this->parser->is_important( $this->sub_value ) ) { 917 $important = '!important'; 918 } 919 $this->sub_value = $this->parser->gvw_important( $this->sub_value ); 920 921 // Compress font-weight 922 if ( $this->property === 'font-weight' && $this->parser->get_cfg( 'compress_font-weight' ) ) { 923 if ( $this->sub_value === 'bold' ) { 924 $this->sub_value = '700'; 925 $this->parser->log( 'Optimised font-weight: Changed "bold" to "700"', 'Information' ); 926 } 927 elseif ( $this->sub_value === 'normal' ) { 928 $this->sub_value = '400'; 929 $this->parser->log( 'Optimised font-weight: Changed "normal" to "400"', 'Information' ); 930 } 931 } 932 933 $temp = $this->compress_numbers( $this->sub_value ); 934 if ( strcasecmp( $temp, $this->sub_value ) !== 0 ) { 935 if ( strlen( $temp ) > strlen( $this->sub_value ) ) { 936 $this->parser->log( 'Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); 937 } 938 else { 939 $this->parser->log( 'Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); 940 } 941 $this->sub_value = $temp; 942 } 943 if ( $this->parser->get_cfg( 'compress_colors' ) ) { 944 $temp = $this->cut_color( $this->sub_value ); 945 if ( $temp !== $this->sub_value ) { 946 if ( isset( $replace_colors[ $this->sub_value ] ) ) { 947 $this->parser->log( 'Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning' ); 948 } 949 else { 950 $this->parser->log( 'Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information' ); 951 } 952 $this->sub_value = $temp; 953 } 954 } 955 $this->sub_value .= $important; 956 } 957 958 /** 959 * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 ) 960 * 961 * @param string $subvalue 962 * 963 * @return string 964 * @version 1.2 965 */ 966 public function compress_numbers( $subvalue ) { 967 $unit_values = &$this->parser->data[ 'csstidy' ][ 'unit_values' ]; 968 $color_values = &$this->parser->data[ 'csstidy' ][ 'color_values' ]; 969 970 // for font:1em/1em sans-serif...; 971 if ( $this->property === 'font' ) { 972 $temp = explode( '/', $subvalue ); 973 } 974 else { 975 $temp = array( $subvalue ); 976 } 977 978 for ( $l = 0; $l < count( $temp ); $l++ ) { 979 // if we are not dealing with a number at this point, do not optimise anything 980 $number = $this->AnalyseCssNumber( $temp[ $l ] ); 981 if ( $number === false ) { 982 return $subvalue; 983 } 984 985 // Fix bad colors 986 if ( in_array( $this->property, $color_values ) ) { 987 $temp[ $l ] = '#' . $temp[ $l ]; 988 continue; 989 } 990 991 if ( abs( $number[ 0 ] ) > 0 ) { 992 if ( $number[ 1 ] == '' && in_array( $this->property, $unit_values, true ) ) { 993 $number[ 1 ] = 'px'; 994 } 995 } 996 elseif ( $number[ 1 ] != 's' && $number[ 1 ] != 'ms' ) { 997 $number[ 1 ] = ''; 998 } 999 1000 $temp[ $l ] = $number[ 0 ] . $number[ 1 ]; 1001 } 1002 1003 return ( ( count( $temp ) > 1 ) ? $temp[ 0 ] . '/' . $temp[ 1 ] : $temp[ 0 ] ); 1004 } 1005 1006 /** 1007 * Checks if a given string is a CSS valid number. If it is, 1008 * an array containing the value and unit is returned 1009 * 1010 * @param string $string 1011 * 1012 * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number 1013 */ 1014 public function AnalyseCssNumber( $string ) { 1015 // most simple checks first 1016 if ( strlen( $string ) == 0 || ctype_alpha( $string[ 0 ] ) ) { 1017 return false; 1018 } 1019 1020 $units = &$this->parser->data[ 'csstidy' ][ 'units' ]; 1021 $return = array( 0, '' ); 1022 1023 $return[ 0 ] = floatval( $string ); 1024 if ( abs( $return[ 0 ] ) > 0 && abs( $return[ 0 ] ) < 1 ) { 1025 if ( $return[ 0 ] < 0 ) { 1026 $return[ 0 ] = '-' . ltrim( substr( $return[ 0 ], 1 ), '0' ); 1027 } 1028 else { 1029 $return[ 0 ] = ltrim( $return[ 0 ], '0' ); 1030 } 1031 } 1032 1033 // Look for unit and split from value if exists 1034 foreach ( $units as $unit ) { 1035 $expectUnitAt = strlen( $string ) - strlen( $unit ); 1036 if ( ! ( $unitInString = stristr( $string, $unit ) ) ) { // mb_strpos() fails with "false" 1037 continue; 1038 } 1039 $actualPosition = strpos( $string, $unitInString ); 1040 if ( $expectUnitAt === $actualPosition ) { 1041 $return[ 1 ] = $unit; 1042 $string = substr( $string, 0, -strlen( $unit ) ); 1043 break; 1044 } 1045 } 1046 if ( ! is_numeric( $string ) ) { 1047 return false; 1048 } 1049 return $return; 1050 } 1051 1052 /** 1053 * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. 1054 * 1055 * @param string $color 1056 * 1057 * @return string 1058 * @version 1.1 1059 */ 1060 public function cut_color( $color ) { 1061 $replace_colors = &$this->parser->data[ 'csstidy' ][ 'replace_colors' ]; 1062 1063 // if it's a string, don't touch ! 1064 if ( strncmp( $color, "'", 1 ) == 0 || strncmp( $color, '"', 1 ) == 0 ) { 1065 return $color; 1066 } 1067 1068 /* expressions complexes de type gradient */ 1069 if ( strpos( $color, '(' ) !== false && strncmp( $color, 'rgb(', 4 ) != 0 ) { 1070 // on ne touche pas aux couleurs dans les expression ms, c'est trop sensible 1071 if ( stripos( $color, 'progid:' ) !== false ) { 1072 return $color; 1073 } 1074 preg_match_all( ",rgb\([^)]+\),i", $color, $matches, PREG_SET_ORDER ); 1075 if ( count( $matches ) ) { 1076 foreach ( $matches as $m ) { 1077 $color = str_replace( $m[ 0 ], $this->cut_color( $m[ 0 ] ), $color ); 1078 } 1079 } 1080 preg_match_all( ",#[0-9a-f]{6}(?=[^0-9a-f]),i", $color, $matches, PREG_SET_ORDER ); 1081 if ( count( $matches ) ) { 1082 foreach ( $matches as $m ) { 1083 $color = str_replace( $m[ 0 ], $this->cut_color( $m[ 0 ] ), $color ); 1084 } 1085 } 1086 return $color; 1087 } 1088 1089 // rgb(0,0,0) -> #000000 (or #000 in this case later) 1090 if ( strncasecmp( $color, 'rgb(', 4 ) == 0 ) { 1091 $color_tmp = substr( $color, 4, strlen( $color ) - 5 ); 1092 $color_tmp = explode( ',', $color_tmp ); 1093 for ( $i = 0; $i < count( $color_tmp ); $i++ ) { 1094 $color_tmp[ $i ] = trim( $color_tmp[ $i ] ); 1095 if ( substr( $color_tmp[ $i ], -1 ) === '%' ) { 1096 $color_tmp[ $i ] = round( ( 255 * $color_tmp[ $i ] ) / 100 ); 1097 } 1098 if ( $color_tmp[ $i ] > 255 ) { 1099 $color_tmp[ $i ] = 255; 1100 } 1101 } 1102 $color = '#'; 1103 for ( $i = 0; $i < 3; $i++ ) { 1104 if ( $color_tmp[ $i ] < 16 ) { 1105 $color .= '0' . dechex( $color_tmp[ $i ] ); 1106 } 1107 else { 1108 $color .= dechex( $color_tmp[ $i ] ); 1109 } 1110 } 1111 } 1112 1113 // Fix bad color names 1114 if ( isset( $replace_colors[ strtolower( $color ) ] ) ) { 1115 $color = $replace_colors[ strtolower( $color ) ]; 1116 } 1117 1118 // #aabbcc -> #abc 1119 if ( strlen( $color ) == 7 ) { 1120 $color_temp = strtolower( $color ); 1121 if ( $color_temp[ 0 ] === '#' && $color_temp[ 1 ] == $color_temp[ 2 ] && $color_temp[ 3 ] == $color_temp[ 4 ] && $color_temp[ 5 ] == $color_temp[ 6 ] ) { 1122 $color = '#' . $color[ 1 ] . $color[ 3 ] . $color[ 5 ]; 1123 } 1124 } 1125 1126 switch ( strtolower( $color ) ) { 1127 /* color name -> hex code */ 1128 case 'black': 1129 return '#000'; 1130 case 'fuchsia': 1131 return '#f0f'; 1132 case 'white': 1133 return '#fff'; 1134 case 'yellow': 1135 return '#ff0'; 1136 1137 /* hex code -> color name */ 1138 case '#800000': 1139 return 'maroon'; 1140 case '#ffa500': 1141 return 'orange'; 1142 case '#808000': 1143 return 'olive'; 1144 case '#800080': 1145 return 'purple'; 1146 case '#008000': 1147 return 'green'; 1148 case '#000080': 1149 return 'navy'; 1150 case '#008080': 1151 return 'teal'; 1152 case '#c0c0c0': 1153 return 'silver'; 1154 case '#808080': 1155 return 'gray'; 1156 case '#f00': 1157 return 'red'; 1158 } 1159 1160 return $color; 1161 } 1162 1163 /** 1164 * Reversing margin shorthands 1165 * 1166 * @param string $value 1167 * 1168 * @return string 1169 */ 1170 public function reverse_left_and_right_margin( $value ) { 1171 return $this->reverse_left_and_right_4value_shorthands( 'margin', $value ); 1172 } 1173 1174 /** 1175 * Reversing 4 values shorthands properties 1176 * 1177 * @param string $value 1178 * 1179 * @return string 1180 */ 1181 public function reverse_left_and_right_4value_shorthands( $property, $value ) { 1182 $shorthands = &$this->parser->data[ 'csstidy' ][ 'shorthands' ]; 1183 if ( isset( $shorthands[ $property ] ) ) { 1184 $property_right = $shorthands[ $property ][ 1 ]; 1185 $property_left = $shorthands[ $property ][ 3 ]; 1186 $v = $this->dissolve_4value_shorthands( $property, $value ); 1187 if ( $v[ $property_left ] !== $v[ $property_right ] ) { 1188 $r = $v[ $property_right ]; 1189 $v[ $property_right ] = $v[ $property_left ]; 1190 $v[ $property_left ] = $r; 1191 $v = $this->merge_4value_shorthands( $v ); 1192 if ( isset( $v[ $property ] ) ) { 1193 return $v[ $property ]; 1194 } 1195 } 1196 } 1197 return $value; 1198 } 1199 1200 /** 1201 * Reversing padding shorthands 1202 * 1203 * @param string $value 1204 * 1205 * @return string 1206 */ 1207 public function reverse_left_and_right_padding( $value ) { 1208 return $this->reverse_left_and_right_4value_shorthands( 'padding', $value ); 1209 } 1210 1211 /** 1212 * Reversing border-color shorthands 1213 * 1214 * @param string $value 1215 * 1216 * @return string 1217 */ 1218 public function reverse_left_and_right_border_color( $value ) { 1219 return $this->reverse_left_and_right_4value_shorthands( 'border-color', $value ); 1220 } 1221 1222 /** 1223 * Reversing border-style shorthands 1224 * 1225 * @param string $value 1226 * 1227 * @return string 1228 */ 1229 public function reverse_left_and_right_border_style( $value ) { 1230 return $this->reverse_left_and_right_4value_shorthands( 'border-style', $value ); 1231 } 1232 1233 /** 1234 * Reversing border-width shorthands 1235 * 1236 * @param string $value 1237 * 1238 * @return string 1239 */ 1240 public function reverse_left_and_right_border_width( $value ) { 1241 return $this->reverse_left_and_right_4value_shorthands( 'border-width', $value ); 1242 } 1243 1244 /** 1245 * Reversing border-radius shorthands 1246 * 1247 * @param string $value 1248 * 1249 * @return string 1250 */ 1251 public function reverse_left_and_right_border_radius( $value ) { 1252 return $this->reverse_left_and_right_4value_radius_shorthands( 'border-radius', $value ); 1253 } 1254 1255 /** 1256 * Reversing 4 values radius shorthands properties 1257 * 1258 * @param string $value 1259 * 1260 * @return string 1261 */ 1262 public function reverse_left_and_right_4value_radius_shorthands( $property, $value ) { 1263 $shorthands = &$this->parser->data[ 'csstidy' ][ 'radius_shorthands' ]; 1264 if ( isset( $shorthands[ $property ] ) ) { 1265 $v = $this->dissolve_4value_radius_shorthands( $property, $value ); 1266 if ( $v[ $shorthands[ $property ][ 0 ] ] !== $v[ $shorthands[ $property ][ 1 ] ] 1267 or $v[ $shorthands[ $property ][ 2 ] ] !== $v[ $shorthands[ $property ][ 3 ] ] ) { 1268 $r = array( 1269 $shorthands[ $property ][ 0 ] => $v[ $shorthands[ $property ][ 1 ] ], 1270 $shorthands[ $property ][ 1 ] => $v[ $shorthands[ $property ][ 0 ] ], 1271 $shorthands[ $property ][ 2 ] => $v[ $shorthands[ $property ][ 3 ] ], 1272 $shorthands[ $property ][ 3 ] => $v[ $shorthands[ $property ][ 2 ] ], 1273 ); 1274 $v = $this->merge_4value_radius_shorthands( $r ); 1275 if ( isset( $v[ $property ] ) ) { 1276 return $v[ $property ]; 1277 } 1278 } 1279 } 1280 return $value; 1281 } 1282 1283 /** 1284 * Dissolves radius properties like 1285 * border-radius:10px 10px 10px / 1px 2px 1286 * to border-top-left:10px 1px;border-top-right:10px 2x;... 1287 * 1288 * @param string $property 1289 * @param string $value 1290 * 1291 * @return array 1292 * @version 1.0 1293 * @use dissolve_4value_shorthands() 1294 * @see merge_4value_radius_shorthands() 1295 */ 1296 public function dissolve_4value_radius_shorthands( $property, $value ) { 1297 $shorthands = &$this->parser->data[ 'csstidy' ][ 'radius_shorthands' ]; 1298 if ( ! is_array( $shorthands[ $property ] ) ) { 1299 $return[ $property ] = $value; 1300 return $return; 1301 } 1302 1303 if ( strpos( $value, '/' ) !== false ) { 1304 $values = $this->explode_ws( '/', $value ); 1305 if ( count( $values ) == 2 ) { 1306 $r[ 0 ] = $this->dissolve_4value_shorthands( $property, trim( $values[ 0 ] ), $shorthands ); 1307 $r[ 1 ] = $this->dissolve_4value_shorthands( $property, trim( $values[ 1 ] ), $shorthands ); 1308 $return = array(); 1309 foreach ( $r[ 0 ] as $p => $v ) { 1310 $return[ $p ] = $v; 1311 if ( $r[ 1 ][ $p ] !== $v ) { 1312 $return[ $p ] .= ' ' . $r[ 1 ][ $p ]; 1313 } 1314 } 1315 return $return; 1316 } 1317 } 1318 1319 $return = $this->dissolve_4value_shorthands( $property, $value, $shorthands ); 1320 return $return; 1321 } 1322 1323 /** 1324 * Reversing border-radius shorthands 1325 * 1326 * @param string $value 1327 * 1328 * @return string 1329 */ 1330 public function reverse_left_and_right__moz_border_radius( $value ) { 1331 return $this->reverse_left_and_right_4value_radius_shorthands( 'border-radius', $value ); 1332 } 1333 1334 /** 1335 * Reversing border-radius shorthands 1336 * 1337 * @param string $value 1338 * 1339 * @return string 1340 */ 1341 public function reverse_left_and_right__webkit_border_radius( $value ) { 1342 return $this->reverse_left_and_right_4value_radius_shorthands( 'border-radius', $value ); 1343 } 1344 1345 1346 /** 1347 * Reversing background shorthands 1348 * 1349 * @param string $value 1350 * 1351 * @return string 1352 */ 1353 public function reverse_left_and_right_background( $value ) { 1354 $values = $this->dissolve_short_bg( $value ); 1355 if ( isset( $values[ 'background-position' ] ) and $values[ 'background-position' ] ) { 1356 $v = $this->reverse_left_and_right_background_position( $values[ 'background-position' ] ); 1357 if ( $v !== $values[ 'background-position' ] ) { 1358 if ( $value == $values[ 'background-position' ] ) { 1359 return $v; 1360 } 1361 else { 1362 $values[ 'background-position' ] = $v; 1363 $x = $this->merge_bg( $values ); 1364 if ( isset( $x[ 'background' ] ) ) { 1365 return $x[ 'background' ]; 1366 } 1367 } 1368 } 1369 } 1370 return $value; 1371 } 1372 1373 /** 1374 * Reversing background position shorthands 1375 * 1376 * @param string $value 1377 * 1378 * @return string 1379 */ 1380 public function reverse_left_and_right_background_position( $value ) { 1381 // multiple background case 1382 if ( strpos( $value, ',' ) !== false ) { 1383 $values = $this->explode_ws( ',', $value ); 1384 if ( count( $values ) > 1 ) { 1385 foreach ( $values as $k => $v ) { 1386 $values[ $k ] = $this->reverse_left_and_right_background_position( $v ); 1387 } 1388 return implode( ',', $values ); 1389 } 1390 } 1391 1392 // if no explicit left or right value 1393 if ( stripos( $value, 'left' ) === false and stripos( $value, 'right' ) === false ) { 1394 $values = $this->explode_ws( ' ', trim( $value ) ); 1395 $values = array_map( 'trim', $values ); 1396 $values = array_filter( $values, function ( $v ) { 1397 return strlen( $v ); 1398 } ); 1399 $values = array_values( $values ); 1400 if ( count( $values ) == 1 ) { 1401 if ( in_array( $value, array( 'center', 'top', 'bottom', 'inherit', 'initial', 'unset' ) ) ) { 1402 return $value; 1403 } 1404 return "left $value"; 1405 } 1406 if ( $values[ 1 ] == 'top' or $values[ 1 ] == 'bottom' ) { 1407 if ( $values[ 0 ] === 'center' ) { 1408 return $value; 1409 } 1410 return 'left ' . implode( ' ', $values ); 1411 } 1412 else { 1413 $last = array_pop( $values ); 1414 if ( $last === 'center' ) { 1415 return $value; 1416 } 1417 return implode( ' ', $values ) . ' left ' . $last; 1418 } 1419 } 1420 1421 return $value; 1422 } 1423 1424 /** 1425 * Reversing background position shorthands 1426 * 1427 * @param string $value 1428 * 1429 * @return string 1430 */ 1431 public function reverse_left_and_right_background_position_x( $value ) { 1432 return $this->reverse_left_and_right_background_position( $value ); 1433 } 1434 1435 } 426 427 $temp[$l] = $number[0] . $number[1]; 428 } 429 430 return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]); 431 } 432 433 /** 434 * Checks if a given string is a CSS valid number. If it is, 435 * an array containing the value and unit is returned 436 * @param string $string 437 * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number 438 */ 439 public function AnalyseCssNumber($string) { 440 // most simple checks first 441 if (strlen($string) == 0 || ctype_alpha($string[0])) { 442 return false; 443 } 444 445 $units = & $this->parser->data['csstidy']['units']; 446 $return = array(0, ''); 447 448 $return[0] = floatval($string); 449 if (abs($return[0]) > 0 && abs($return[0]) < 1) { 450 if ($return[0] < 0) { 451 $return[0] = '-' . ltrim(substr($return[0], 1), '0'); 452 } else { 453 $return[0] = ltrim($return[0], '0'); 454 } 455 } 456 457 // Look for unit and split from value if exists 458 foreach ($units as $unit) { 459 $expectUnitAt = strlen($string) - strlen($unit); 460 if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false" 461 continue; 462 } 463 $actualPosition = strpos($string, $unitInString); 464 if ($expectUnitAt === $actualPosition) { 465 $return[1] = $unit; 466 $string = substr($string, 0, - strlen($unit)); 467 break; 468 } 469 } 470 if (!is_numeric($string)) { 471 return false; 472 } 473 return $return; 474 } 475 476 /** 477 * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red} 478 * Very basic and has at least one bug. Hopefully there is a replacement soon. 479 * @param array $array 480 * @return array 481 * @access public 482 * @version 1.2 483 */ 484 public function merge_selectors(&$array) { 485 $css = $array; 486 foreach ($css as $key => $value) { 487 if (!isset($css[$key])) { 488 continue; 489 } 490 $newsel = ''; 491 492 // Check if properties also exist in another selector 493 $keys = array(); 494 // PHP bug (?) without $css = $array; here 495 foreach ($css as $selector => $vali) { 496 if ($selector == $key) { 497 continue; 498 } 499 500 if ($css[$key] === $vali) { 501 $keys[] = $selector; 502 } 503 } 504 505 if (!empty($keys)) { 506 $newsel = $key; 507 unset($css[$key]); 508 foreach ($keys as $selector) { 509 unset($css[$selector]); 510 $newsel .= ',' . $selector; 511 } 512 $css[$newsel] = $value; 513 } 514 } 515 $array = $css; 516 } 517 518 /** 519 * Removes invalid selectors and their corresponding rule-sets as 520 * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check 521 * and should be replaced by a full-blown parsing algorithm or 522 * regular expression 523 * @version 1.4 524 */ 525 public function discard_invalid_selectors(&$array) { 526 $invalid = array('+' => true, '~' => true, ',' => true, '>' => true); 527 foreach ($array as $selector => $decls) { 528 $ok = true; 529 $selectors = array_map('trim', explode(',', $selector)); 530 foreach ($selectors as $s) { 531 $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s); 532 foreach ($simple_selectors as $ss) { 533 if ($ss === '') 534 $ok = false; 535 // could also check $ss for internal structure, 536 // but that probably would be too slow 537 } 538 } 539 if (!$ok) 540 unset($array[$selector]); 541 } 542 } 543 544 /** 545 * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;... 546 * @param string $property 547 * @param string $value 548 * @param array|null $shorthands 549 * @return array 550 * @version 1.0 551 * @see merge_4value_shorthands() 552 */ 553 public function dissolve_4value_shorthands($property, $value, $shorthands = null) { 554 if (is_null($shorthands)) { 555 $shorthands = & $this->parser->data['csstidy']['shorthands']; 556 } 557 if (!is_array($shorthands[$property])) { 558 $return[$property] = $value; 559 return $return; 560 } 561 562 $important = ''; 563 if ($this->parser->is_important($value)) { 564 $value = $this->parser->gvw_important($value); 565 $important = '!important'; 566 } 567 $values = explode(' ', $value); 568 569 570 $return = array(); 571 if (count($values) == 4) { 572 for ($i = 0; $i < 4; $i++) { 573 $return[$shorthands[$property][$i]] = $values[$i] . $important; 574 } 575 } elseif (count($values) == 3) { 576 $return[$shorthands[$property][0]] = $values[0] . $important; 577 $return[$shorthands[$property][1]] = $values[1] . $important; 578 $return[$shorthands[$property][3]] = $values[1] . $important; 579 $return[$shorthands[$property][2]] = $values[2] . $important; 580 } elseif (count($values) == 2) { 581 for ($i = 0; $i < 4; $i++) { 582 $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important; 583 } 584 } else { 585 for ($i = 0; $i < 4; $i++) { 586 $return[$shorthands[$property][$i]] = $values[0] . $important; 587 } 588 } 589 590 return $return; 591 } 592 593 /** 594 * Dissolves radius properties like 595 * border-radius:10px 10px 10px / 1px 2px 596 * to border-top-left:10px 1px;border-top-right:10px 2x;... 597 * @param string $property 598 * @param string $value 599 * @return array 600 * @version 1.0 601 * @use dissolve_4value_shorthands() 602 * @see merge_4value_radius_shorthands() 603 */ 604 public function dissolve_4value_radius_shorthands($property, $value) { 605 $shorthands = & $this->parser->data['csstidy']['radius_shorthands']; 606 if (!is_array($shorthands[$property])) { 607 $return[$property] = $value; 608 return $return; 609 } 610 611 if (strpos($value, '/') !== false) { 612 $values = $this->explode_ws('/', $value); 613 if (count($values) == 2) { 614 $r[0] = $this->dissolve_4value_shorthands($property, trim($values[0]), $shorthands); 615 $r[1] = $this->dissolve_4value_shorthands($property, trim($values[1]), $shorthands); 616 $return = array(); 617 foreach ($r[0] as $p=>$v) { 618 $return[$p] = $v; 619 if ($r[1][$p] !== $v) { 620 $return[$p] .= ' ' . $r[1][$p]; 621 } 622 } 623 return $return; 624 } 625 } 626 627 $return = $this->dissolve_4value_shorthands($property, $value, $shorthands); 628 return $return; 629 } 630 631 /** 632 * Explodes a string as explode() does, however, not if $sep is escaped or within a string. 633 * @param string $sep seperator 634 * @param string $string 635 * @param bool $explode_in_parenthesis 636 * @return array 637 * @version 1.0 638 */ 639 public function explode_ws($sep, $string, $explode_in_parenthesis = false) { 640 $status = 'st'; 641 $to = ''; 642 643 $output = array( 644 0 => '', 645 ); 646 $num = 0; 647 for ($i = 0, $len = strlen($string); $i < $len; $i++) { 648 switch ($status) { 649 case 'st': 650 if ($string[$i] == $sep && !$this->parser->escaped($string, $i)) { 651 ++$num; 652 } elseif ($string[$i] === '"' || $string[$i] === '\'' || (!$explode_in_parenthesis && $string[$i] === '(') && !$this->parser->escaped($string, $i)) { 653 $status = 'str'; 654 $to = ($string[$i] === '(') ? ')' : $string[$i]; 655 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i]; 656 } else { 657 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i]; 658 } 659 break; 660 661 case 'str': 662 if ($string[$i] == $to && !$this->parser->escaped($string, $i)) { 663 $status = 'st'; 664 } 665 (isset($output[$num])) ? $output[$num] .= $string[$i] : $output[$num] = $string[$i]; 666 break; 667 } 668 } 669 670 return $output; 671 } 672 673 /** 674 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() 675 * @param array $array 676 * @param array|null $shorthands 677 * @return array 678 * @version 1.2 679 * @see dissolve_4value_shorthands() 680 */ 681 public function merge_4value_shorthands($array, $shorthands = null) { 682 $return = $array; 683 if (is_null($shorthands)) { 684 $shorthands = & $this->parser->data['csstidy']['shorthands']; 685 } 686 687 foreach ($shorthands as $key => $value) { 688 if ($value !== 0 && isset($array[$value[0]]) && isset($array[$value[1]]) 689 && isset($array[$value[2]]) && isset($array[$value[3]])) { 690 $return[$key] = ''; 691 692 $important = ''; 693 for ($i = 0; $i < 4; $i++) { 694 $val = $array[$value[$i]]; 695 if ($this->parser->is_important($val)) { 696 $important = '!important'; 697 $return[$key] .= $this->parser->gvw_important($val) . ' '; 698 } else { 699 $return[$key] .= $val . ' '; 700 } 701 unset($return[$value[$i]]); 702 } 703 $return[$key] = $this->shorthand(trim($return[$key] . $important)); 704 } 705 } 706 return $return; 707 } 708 709 /** 710 * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() 711 * @param array $array 712 * @return array 713 * @version 1.2 714 * @use merge_4value_shorthands() 715 * @see dissolve_4value_radius_shorthands() 716 */ 717 public function merge_4value_radius_shorthands($array) { 718 $return = $array; 719 $shorthands = & $this->parser->data['csstidy']['radius_shorthands']; 720 721 foreach ($shorthands as $key => $value) { 722 if (isset($array[$value[0]]) && isset($array[$value[1]]) 723 && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) { 724 $return[$key] = ''; 725 $a = array(); 726 for ($i = 0; $i < 4; $i++) { 727 $v = $this->explode_ws(' ', trim($array[$value[$i]])); 728 $a[0][$value[$i]] = reset($v); 729 $a[1][$value[$i]] = end($v); 730 } 731 $r = array(); 732 $r[0] = $this->merge_4value_shorthands($a[0], $shorthands); 733 $r[1] = $this->merge_4value_shorthands($a[1], $shorthands); 734 735 if (isset($r[0][$key]) and isset($r[1][$key])) { 736 $return[$key] = $r[0][$key]; 737 if ($r[1][$key] !== $r[0][$key]) { 738 $return[$key] .= ' / ' . $r[1][$key]; 739 } 740 for ($i = 0; $i < 4; $i++) { 741 unset($return[$value[$i]]); 742 } 743 } 744 } 745 } 746 return $return; 747 } 748 /** 749 * Dissolve background property 750 * @param string $str_value 751 * @return array 752 * @version 1.0 753 * @see merge_bg() 754 * @todo full CSS 3 compliance 755 */ 756 public function dissolve_short_bg($str_value) { 757 // don't try to explose background gradient ! 758 if (stripos($str_value, 'gradient(')!== false) 759 return array('background'=>$str_value); 760 761 $background_prop_default = & $this->parser->data['csstidy']['background_prop_default']; 762 $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space'); 763 $attachment = array('scroll', 'fixed', 'local'); 764 $clip = array('border', 'padding'); 765 $origin = array('border', 'padding', 'content'); 766 $pos = array('top', 'center', 'bottom', 'left', 'right'); 767 $important = ''; 768 $return = array('background-image' => null, 'background-size' => null, 'background-repeat' => null, 'background-position' => null, 'background-attachment' => null, 'background-clip' => null, 'background-origin' => null, 'background-color' => null); 769 770 if ($this->parser->is_important($str_value)) { 771 $important = ' !important'; 772 $str_value = $this->parser->gvw_important($str_value); 773 } 774 775 $str_value = $this->explode_ws(',', $str_value); 776 for ($i = 0; $i < count($str_value); $i++) { 777 $have['clip'] = false; 778 $have['pos'] = false; 779 $have['color'] = false; 780 $have['bg'] = false; 781 782 if (is_array($str_value[$i])) { 783 $str_value[$i] = $str_value[$i][0]; 784 } 785 $str_value[$i] = $this->explode_ws(' ', trim($str_value[$i])); 786 787 for ($j = 0; $j < count($str_value[$i]); $j++) { 788 if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) { 789 $return['background-image'] .= $str_value[$i][$j] . ','; 790 $have['bg'] = true; 791 } elseif (in_array($str_value[$i][$j], $repeat, true)) { 792 $return['background-repeat'] .= $str_value[$i][$j] . ','; 793 } elseif (in_array($str_value[$i][$j], $attachment, true)) { 794 $return['background-attachment'] .= $str_value[$i][$j] . ','; 795 } elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) { 796 $return['background-clip'] .= $str_value[$i][$j] . ','; 797 $have['clip'] = true; 798 } elseif (in_array($str_value[$i][$j], $origin, true)) { 799 $return['background-origin'] .= $str_value[$i][$j] . ','; 800 } elseif ($str_value[$i][$j][0] === '(') { 801 $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ','; 802 } elseif (in_array($str_value[$i][$j], $pos, true) || is_numeric($str_value[$i][$j][0]) || $str_value[$i][$j][0] === null || $str_value[$i][$j][0] === '-' || $str_value[$i][$j][0] === '.') { 803 $return['background-position'] .= $str_value[$i][$j]; 804 if (!$have['pos']) 805 $return['background-position'] .= ' '; else 806 $return['background-position'].= ','; 807 $have['pos'] = true; 808 } elseif (!$have['color']) { 809 $return['background-color'] .= $str_value[$i][$j] . ','; 810 $have['color'] = true; 811 } 812 } 813 } 814 815 foreach ($background_prop_default as $bg_prop => $default_value) { 816 if ($return[$bg_prop] !== null) { 817 $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important; 818 } 819 else 820 $return[$bg_prop] = $default_value . $important; 821 } 822 return $return; 823 } 824 825 /** 826 * Merges all background properties 827 * @param array $input_css 828 * @return array 829 * @version 1.0 830 * @see dissolve_short_bg() 831 * @todo full CSS 3 compliance 832 */ 833 public function merge_bg($input_css) { 834 $background_prop_default = & $this->parser->data['csstidy']['background_prop_default']; 835 // Max number of background images. CSS3 not yet fully implemented 836 $number_of_values = @max(count($this->explode_ws(',', $input_css['background-image'])), count($this->explode_ws(',', $input_css['background-color'])), 1); 837 // Array with background images to check if BG image exists 838 $bg_img_array = @$this->explode_ws(',', $this->parser->gvw_important($input_css['background-image'])); 839 $new_bg_value = ''; 840 $important = ''; 841 842 // if background properties is here and not empty, don't try anything 843 if (isset($input_css['background']) && $input_css['background']) 844 return $input_css; 845 846 for ($i = 0; $i < $number_of_values; $i++) { 847 foreach ($background_prop_default as $bg_property => $default_value) { 848 // Skip if property does not exist 849 if (!isset($input_css[$bg_property])) { 850 continue; 851 } 852 853 $cur_value = $input_css[$bg_property]; 854 // skip all optimisation if gradient() somewhere 855 if (stripos($cur_value, 'gradient(') !== false) 856 return $input_css; 857 858 // Skip some properties if there is no background image 859 if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none') 860 && ($bg_property === 'background-size' || $bg_property === 'background-position' 861 || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')) { 862 continue; 863 } 864 865 // Remove !important 866 if ($this->parser->is_important($cur_value)) { 867 $important = ' !important'; 868 $cur_value = $this->parser->gvw_important($cur_value); 869 } 870 871 // Do not add default values 872 if ($cur_value === $default_value) { 873 continue; 874 } 875 876 $temp = $this->explode_ws(',', $cur_value); 877 878 if (isset($temp[$i])) { 879 if ($bg_property === 'background-size') { 880 $new_bg_value .= '(' . $temp[$i] . ') '; 881 } else { 882 $new_bg_value .= $temp[$i] . ' '; 883 } 884 } 885 } 886 887 $new_bg_value = trim($new_bg_value); 888 if ($i != $number_of_values - 1) 889 $new_bg_value .= ','; 890 } 891 892 // Delete all background-properties 893 foreach ($background_prop_default as $bg_property => $default_value) { 894 unset($input_css[$bg_property]); 895 } 896 897 // Add new background property 898 if ($new_bg_value !== '') 899 $input_css['background'] = $new_bg_value . $important; 900 elseif(isset ($input_css['background'])) 901 $input_css['background'] = 'none'; 902 903 return $input_css; 904 } 905 906 /** 907 * Dissolve font property 908 * @param string $str_value 909 * @return array 910 * @version 1.3 911 * @see merge_font() 912 */ 913 public function dissolve_short_font($str_value) { 914 $font_prop_default = & $this->parser->data['csstidy']['font_prop_default']; 915 $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900); 916 $font_variant = array('normal', 'small-caps'); 917 $font_style = array('normal', 'italic', 'oblique'); 918 $important = ''; 919 $return = array('font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null); 920 921 if ($this->parser->is_important($str_value)) { 922 $important = '!important'; 923 $str_value = $this->parser->gvw_important($str_value); 924 } 925 926 $have['style'] = false; 927 $have['variant'] = false; 928 $have['weight'] = false; 929 $have['size'] = false; 930 // Detects if font-family consists of several words w/o quotes 931 $multiwords = false; 932 933 // Workaround with multiple font-family 934 $str_value = $this->explode_ws(',', trim($str_value)); 935 936 $str_value[0] = $this->explode_ws(' ', trim($str_value[0])); 937 938 for ($j = 0; $j < count($str_value[0]); $j++) { 939 if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) { 940 $return['font-weight'] = $str_value[0][$j]; 941 $have['weight'] = true; 942 } elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) { 943 $return['font-variant'] = $str_value[0][$j]; 944 $have['variant'] = true; 945 } elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) { 946 $return['font-style'] = $str_value[0][$j]; 947 $have['style'] = true; 948 } elseif ($have['size'] === false && (is_numeric($str_value[0][$j][0]) || $str_value[0][$j][0] === null || $str_value[0][$j][0] === '.')) { 949 $size = $this->explode_ws('/', trim($str_value[0][$j])); 950 $return['font-size'] = $size[0]; 951 if (isset($size[1])) { 952 $return['line-height'] = $size[1]; 953 } else { 954 $return['line-height'] = ''; // don't add 'normal' ! 955 } 956 $have['size'] = true; 957 } else { 958 if (isset($return['font-family'])) { 959 $return['font-family'] .= ' ' . $str_value[0][$j]; 960 $multiwords = true; 961 } else { 962 $return['font-family'] = $str_value[0][$j]; 963 } 964 } 965 } 966 // add quotes if we have several qords in font-family 967 if ($multiwords !== false) { 968 $return['font-family'] = '"' . $return['font-family'] . '"'; 969 } 970 $i = 1; 971 while (isset($str_value[$i])) { 972 $return['font-family'] .= ',' . trim($str_value[$i]); 973 $i++; 974 } 975 976 // Fix for 100 and more font-size 977 if ($have['size'] === false && isset($return['font-weight']) && 978 is_numeric($return['font-weight'][0])) { 979 $return['font-size'] = $return['font-weight']; 980 unset($return['font-weight']); 981 } 982 983 foreach ($font_prop_default as $font_prop => $default_value) { 984 if ($return[$font_prop] !== null) { 985 $return[$font_prop] = $return[$font_prop] . $important; 986 } 987 else 988 $return[$font_prop] = $default_value . $important; 989 } 990 return $return; 991 } 992 993 /** 994 * Merges all fonts properties 995 * @param array $input_css 996 * @return array 997 * @version 1.3 998 * @see dissolve_short_font() 999 */ 1000 public function merge_font($input_css) { 1001 $font_prop_default = & $this->parser->data['csstidy']['font_prop_default']; 1002 $new_font_value = ''; 1003 $important = ''; 1004 // Skip if not font-family and font-size set 1005 if (isset($input_css['font-family']) && isset($input_css['font-size']) && $input_css['font-family'] != 'inherit') { 1006 // fix several words in font-family - add quotes 1007 if (isset($input_css['font-family'])) { 1008 $families = explode(',', $input_css['font-family']); 1009 $result_families = array(); 1010 foreach ($families as $family) { 1011 $family = trim($family); 1012 $len = strlen($family); 1013 if (strpos($family, ' ') && 1014 !(($family[0] === '"' && $family[$len - 1] === '"') || 1015 ($family[0] === "'" && $family[$len - 1] === "'"))) { 1016 $family = '"' . $family . '"'; 1017 } 1018 $result_families[] = $family; 1019 } 1020 $input_css['font-family'] = implode(',', $result_families); 1021 } 1022 foreach ($font_prop_default as $font_property => $default_value) { 1023 1024 // Skip if property does not exist 1025 if (!isset($input_css[$font_property])) { 1026 continue; 1027 } 1028 1029 $cur_value = $input_css[$font_property]; 1030 1031 // Skip if default value is used 1032 if ($cur_value === $default_value) { 1033 continue; 1034 } 1035 1036 // Remove !important 1037 if ($this->parser->is_important($cur_value)) { 1038 $important = '!important'; 1039 $cur_value = $this->parser->gvw_important($cur_value); 1040 } 1041 1042 $new_font_value .= $cur_value; 1043 // Add delimiter 1044 $new_font_value .= ( $font_property === 'font-size' && 1045 isset($input_css['line-height'])) ? '/' : ' '; 1046 } 1047 1048 $new_font_value = trim($new_font_value); 1049 1050 // Delete all font-properties 1051 foreach ($font_prop_default as $font_property => $default_value) { 1052 if ($font_property !== 'font' || !$new_font_value) 1053 unset($input_css[$font_property]); 1054 } 1055 1056 // Add new font property 1057 if ($new_font_value !== '') { 1058 $input_css['font'] = $new_font_value . $important; 1059 } 1060 } 1061 1062 return $input_css; 1063 } 1064 1065 /** 1066 * Reverse left vs right in a list of properties/values 1067 * @param array $array 1068 * @return array 1069 */ 1070 public function reverse_left_and_right($array) { 1071 $return = array(); 1072 1073 // change left <-> right in properties name and values 1074 foreach ($array as $propertie => $value) { 1075 1076 if (method_exists($this, $m = 'reverse_left_and_right_' . str_replace('-','_',trim($propertie)))) { 1077 $value = $this->$m($value); 1078 } 1079 1080 // simple replacement for properties 1081 $propertie = str_ireplace(array('left', 'right' ,"\x1"), array("\x1", 'left', 'right') , $propertie); 1082 // be careful for values, not modifying protected or quoted valued 1083 foreach (array('left' => "\x1", 'right' => 'left', "\x1" => 'right') as $v => $r) { 1084 if (strpos($value, $v) !== false) { 1085 // attraper les left et right separes du reste (pas au milieu d'un mot) 1086 if (in_array($v, array('left', 'right') )) { 1087 $value = preg_replace(",\\b$v\\b,", "\x0" , $value); 1088 } 1089 else { 1090 $value = str_replace($v, "\x0" , $value); 1091 } 1092 $value = $this->explode_ws("\x0", $value . ' ', true); 1093 $value = rtrim(implode($r, $value)); 1094 $value = str_replace("\x0" , $v, $value); 1095 } 1096 } 1097 $return[$propertie] = $value; 1098 } 1099 1100 return $return; 1101 } 1102 1103 /** 1104 * Reversing 4 values shorthands properties 1105 * @param string $value 1106 * @return string 1107 */ 1108 public function reverse_left_and_right_4value_shorthands($property, $value) { 1109 $shorthands = & $this->parser->data['csstidy']['shorthands']; 1110 if (isset($shorthands[$property])) { 1111 $property_right = $shorthands[$property][1]; 1112 $property_left = $shorthands[$property][3]; 1113 $v = $this->dissolve_4value_shorthands($property, $value); 1114 if ($v[$property_left] !== $v[$property_right]) { 1115 $r = $v[$property_right]; 1116 $v[$property_right] = $v[$property_left]; 1117 $v[$property_left] = $r; 1118 $v = $this->merge_4value_shorthands($v); 1119 if (isset($v[$property])) { 1120 return $v[$property]; 1121 } 1122 } 1123 } 1124 return $value; 1125 } 1126 1127 /** 1128 * Reversing 4 values radius shorthands properties 1129 * @param string $value 1130 * @return string 1131 */ 1132 public function reverse_left_and_right_4value_radius_shorthands($property, $value) { 1133 $shorthands = & $this->parser->data['csstidy']['radius_shorthands']; 1134 if (isset($shorthands[$property])) { 1135 $v = $this->dissolve_4value_radius_shorthands($property, $value); 1136 if ($v[$shorthands[$property][0]] !== $v[$shorthands[$property][1]] 1137 or $v[$shorthands[$property][2]] !== $v[$shorthands[$property][3]]) { 1138 $r = array( 1139 $shorthands[$property][0] => $v[$shorthands[$property][1]], 1140 $shorthands[$property][1] => $v[$shorthands[$property][0]], 1141 $shorthands[$property][2] => $v[$shorthands[$property][3]], 1142 $shorthands[$property][3] => $v[$shorthands[$property][2]], 1143 ); 1144 $v = $this->merge_4value_radius_shorthands($r); 1145 if (isset($v[$property])) { 1146 return $v[$property]; 1147 } 1148 } 1149 } 1150 return $value; 1151 } 1152 1153 /** 1154 * Reversing margin shorthands 1155 * @param string $value 1156 * @return string 1157 */ 1158 public function reverse_left_and_right_margin($value) { 1159 return $this->reverse_left_and_right_4value_shorthands('margin', $value); 1160 } 1161 1162 /** 1163 * Reversing padding shorthands 1164 * @param string $value 1165 * @return string 1166 */ 1167 public function reverse_left_and_right_padding($value) { 1168 return $this->reverse_left_and_right_4value_shorthands('padding', $value); 1169 } 1170 1171 /** 1172 * Reversing border-color shorthands 1173 * @param string $value 1174 * @return string 1175 */ 1176 public function reverse_left_and_right_border_color($value) { 1177 return $this->reverse_left_and_right_4value_shorthands('border-color', $value); 1178 } 1179 1180 /** 1181 * Reversing border-style shorthands 1182 * @param string $value 1183 * @return string 1184 */ 1185 public function reverse_left_and_right_border_style($value) { 1186 return $this->reverse_left_and_right_4value_shorthands('border-style', $value); 1187 } 1188 1189 /** 1190 * Reversing border-width shorthands 1191 * @param string $value 1192 * @return string 1193 */ 1194 public function reverse_left_and_right_border_width($value) { 1195 return $this->reverse_left_and_right_4value_shorthands('border-width', $value); 1196 } 1197 1198 /** 1199 * Reversing border-radius shorthands 1200 * @param string $value 1201 * @return string 1202 */ 1203 public function reverse_left_and_right_border_radius($value) { 1204 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value); 1205 } 1206 1207 /** 1208 * Reversing border-radius shorthands 1209 * @param string $value 1210 * @return string 1211 */ 1212 public function reverse_left_and_right__moz_border_radius($value) { 1213 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value); 1214 } 1215 1216 /** 1217 * Reversing border-radius shorthands 1218 * @param string $value 1219 * @return string 1220 */ 1221 public function reverse_left_and_right__webkit_border_radius($value) { 1222 return $this->reverse_left_and_right_4value_radius_shorthands('border-radius', $value); 1223 } 1224 1225 1226 /** 1227 * Reversing background shorthands 1228 * @param string $value 1229 * @return string 1230 */ 1231 public function reverse_left_and_right_background($value) { 1232 $values = $this->dissolve_short_bg($value); 1233 if (isset($values['background-position']) and $values['background-position']) { 1234 $v = $this->reverse_left_and_right_background_position($values['background-position']); 1235 if ($v !== $values['background-position']) { 1236 if ($value == $values['background-position']) { 1237 return $v; 1238 } 1239 else { 1240 $values['background-position'] = $v; 1241 $x = $this->merge_bg($values); 1242 if (isset($x['background'])) { 1243 return $x['background']; 1244 } 1245 } 1246 } 1247 } 1248 return $value; 1249 } 1250 1251 /** 1252 * Reversing background position shorthands 1253 * @param string $value 1254 * @return string 1255 */ 1256 public function reverse_left_and_right_background_position_x($value) { 1257 return $this->reverse_left_and_right_background_position($value); 1258 } 1259 1260 /** 1261 * Reversing background position shorthands 1262 * @param string $value 1263 * @return string 1264 */ 1265 public function reverse_left_and_right_background_position($value) { 1266 // multiple background case 1267 if (strpos($value, ',') !== false) { 1268 $values = $this->explode_ws(',', $value); 1269 if (count($values) > 1) { 1270 foreach ($values as $k=>$v) { 1271 $values[$k] = $this->reverse_left_and_right_background_position($v); 1272 } 1273 return implode(',', $values); 1274 } 1275 } 1276 1277 // if no explicit left or right value 1278 if (stripos($value, 'left') === false and stripos($value, 'right') === false) { 1279 $values = $this->explode_ws(' ', trim($value)); 1280 $values = array_map('trim', $values); 1281 $values = array_filter($values, function ($v) { return strlen($v);}); 1282 $values = array_values($values); 1283 if (count($values) == 1) { 1284 if (in_array($value, array('center', 'top', 'bottom', 'inherit', 'initial', 'unset'))) { 1285 return $value; 1286 } 1287 return "left $value"; 1288 } 1289 if ($values[1] == 'top' or $values[1] == 'bottom') { 1290 if ($values[0] === 'center') { 1291 return $value; 1292 } 1293 return 'left ' . implode(' ', $values); 1294 } 1295 else { 1296 $last = array_pop($values); 1297 if ($last === 'center') { 1298 return $value; 1299 } 1300 return implode(' ', $values) . ' left ' . $last; 1301 } 1302 } 1303 1304 return $value; 1305 } 1306 1307 } -
opt-out-for-google-analytics/trunk/lib/csstidy/class.csstidy_print.php
r2518394 r2787042 1 1 <?php 2 // If this file is called directly, abort. 3 defined( 'WPINC' ) || die; 4 5 /** 6 * CSSTidy - CSS Parser and Optimiser 7 * 8 * CSS Printing class 9 * This class prints CSS data generated by csstidy. 10 * 11 * Copyright 2005, 2006, 2007 Florian Schmitz 12 * 13 * This file is part of CSSTidy. 14 * 15 * CSSTidy is free software; you can redistribute it and/or modify 16 * it under the terms of the GNU Lesser General Public License as published by 17 * the Free Software Foundation; either version 2.1 of the License, or 18 * (at your option) any later version. 19 * 20 * CSSTidy is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU Lesser General Public License for more details. 24 * 25 * You should have received a copy of the GNU Lesser General Public License 26 * along with this program. If not, see <http://www.gnu.org/licenses/>. 27 * 28 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 29 * @package csstidy 30 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 31 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 32 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 33 */ 34 35 /** 36 * CSS Printing class 37 * 38 * This class prints CSS data generated by csstidy. 39 * 40 * @package csstidy 41 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 42 * @version 1.1.0 43 */ 44 class csstidy_print { 45 46 /** 47 * csstidy object 48 * @var object 49 */ 50 public $parser; 51 52 /** 53 * Saves the input CSS string 54 * @var string 55 * @access private 56 */ 57 public $input_css = ''; 58 /** 59 * Saves the formatted CSS string 60 * @var string 61 * @access public 62 */ 63 public $output_css = ''; 64 /** 65 * Saves the formatted CSS string (plain text) 66 * @var string 67 * @access public 68 */ 69 public $output_css_plain = ''; 70 71 /** 72 * Constructor 73 * 74 * @param array $css contains the class csstidy 75 * 76 * @access private 77 * @version 1.0 78 */ 79 public function __construct( $css ) { 80 $this->parser = $css; 81 $this->css = &$css->css; 82 $this->template = &$css->template; 83 $this->tokens = &$css->tokens; 84 $this->charset = &$css->charset; 85 $this->import = &$css->import; 86 $this->namespace = &$css->namespace; 87 } 88 89 /** 90 * Resets output_css and output_css_plain (new css code) 91 * @access private 92 * @version 1.0 93 */ 94 public function _reset() { 95 $this->output_css = ''; 96 $this->output_css_plain = ''; 97 } 98 99 /** 100 * Returns the CSS code as plain text 101 * 102 * @param string $default_media default @media to add to selectors without any @media 103 * 104 * @return string 105 * @access public 106 * @version 1.0 107 */ 108 public function plain( $default_media = '' ) { 109 $this->_print( true, $default_media ); 110 return $this->output_css_plain; 111 } 112 113 /** 114 * Returns the formatted CSS code 115 * 116 * @param string $default_media default @media to add to selectors without any @media 117 * 118 * @return string 119 * @access public 120 * @version 1.0 121 */ 122 public function formatted( $default_media = '' ) { 123 $this->_print( false, $default_media ); 124 return $this->output_css; 125 } 126 127 /** 128 * Returns the formatted CSS code to make a complete webpage 129 * 130 * @param string $doctype shorthand for the document type 131 * @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet 132 * @param string $title title to be added in the head of the document 133 * @param string $lang two-letter language code to be added to the output 134 * 135 * @return string 136 * @access public 137 * @version 1.4 138 */ 139 public function formatted_page( $doctype = 'html5', $externalcss = true, $title = '', $lang = 'en' ) { 140 switch ( $doctype ) { 141 case 'html5': 142 $doctype_output = '<!DOCTYPE html>'; 143 break; 144 case 'xhtml1.0strict': 145 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 2 3 /** 4 * CSSTidy - CSS Parser and Optimiser 5 * 6 * CSS Printing class 7 * This class prints CSS data generated by csstidy. 8 * 9 * Copyright 2005, 2006, 2007 Florian Schmitz 10 * 11 * This file is part of CSSTidy. 12 * 13 * CSSTidy is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU Lesser General Public License as published by 15 * the Free Software Foundation; either version 2.1 of the License, or 16 * (at your option) any later version. 17 * 18 * CSSTidy is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU Lesser General Public License for more details. 22 * 23 * You should have received a copy of the GNU Lesser General Public License 24 * along with this program. If not, see <http://www.gnu.org/licenses/>. 25 * 26 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 27 * @package csstidy 28 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 29 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 30 * @author Cedric Morin (cedric at yterium dot com) 2010-2012 31 */ 32 33 /** 34 * CSS Printing class 35 * 36 * This class prints CSS data generated by csstidy. 37 * 38 * @package csstidy 39 * @author Florian Schmitz (floele at gmail dot com) 2005-2006 40 * @version 1.1.0 41 */ 42 class csstidy_print { 43 44 /** 45 * csstidy object 46 * @var object 47 */ 48 public $parser; 49 50 /** 51 * Saves the input CSS string 52 * @var string 53 * @access private 54 */ 55 public $input_css = ''; 56 /** 57 * Saves the formatted CSS string 58 * @var string 59 * @access public 60 */ 61 public $output_css = ''; 62 /** 63 * Saves the formatted CSS string (plain text) 64 * @var string 65 * @access public 66 */ 67 public $output_css_plain = ''; 68 69 /** 70 * Constructor 71 * @param array $css contains the class csstidy 72 * @access private 73 * @version 1.0 74 */ 75 public function __construct($css) { 76 $this->parser = $css; 77 $this->css = & $css->css; 78 $this->template = & $css->template; 79 $this->tokens = & $css->tokens; 80 $this->charset = & $css->charset; 81 $this->import = & $css->import; 82 $this->namespace = & $css->namespace; 83 } 84 85 /** 86 * Resets output_css and output_css_plain (new css code) 87 * @access private 88 * @version 1.0 89 */ 90 public function _reset() { 91 $this->output_css = ''; 92 $this->output_css_plain = ''; 93 } 94 95 /** 96 * Returns the CSS code as plain text 97 * @param string $default_media default @media to add to selectors without any @media 98 * @return string 99 * @access public 100 * @version 1.0 101 */ 102 public function plain($default_media='') { 103 $this->_print(true, $default_media); 104 return $this->output_css_plain; 105 } 106 107 /** 108 * Returns the formatted CSS code 109 * @param string $default_media default @media to add to selectors without any @media 110 * @return string 111 * @access public 112 * @version 1.0 113 */ 114 public function formatted($default_media='') { 115 $this->_print(false, $default_media); 116 return $this->output_css; 117 } 118 119 /** 120 * Returns the formatted CSS code to make a complete webpage 121 * @param string $doctype shorthand for the document type 122 * @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet 123 * @param string $title title to be added in the head of the document 124 * @param string $lang two-letter language code to be added to the output 125 * @return string 126 * @access public 127 * @version 1.4 128 */ 129 public function formatted_page($doctype='html5', $externalcss=true, $title='', $lang='en') { 130 switch ($doctype) { 131 case 'html5': 132 $doctype_output = '<!DOCTYPE html>'; 133 break; 134 case 'xhtml1.0strict': 135 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 146 136 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; 147 break;148 case 'xhtml1.1':149 default:150 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"137 break; 138 case 'xhtml1.1': 139 default: 140 $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 151 141 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'; 152 break; 153 } 154 155 $output = $cssparsed = ''; 156 $this->output_css_plain = &$output; 157 158 $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"'; 159 $output .= ( $doctype === 'xhtml1.1' ) ? '>' : ' lang="' . $lang . '">'; 160 $output .= "\n<head>\n <title>$title</title>"; 161 162 if ( $externalcss ) { 163 $output .= "\n <style type=\"text/css\">\n"; 164 $cssparsed = file_get_contents( 'cssparsed.css' ); 165 $output .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php 166 $output .= "\n</style>"; 167 } 168 else { 169 $output .= "\n" . ' <link rel="stylesheet" type="text/css" href="cssparsed.css" />'; 170 } 171 $output .= "\n</head>\n<body><code id=\"copytext\">"; 172 $output .= $this->formatted(); 173 $output .= '</code>' . "\n" . '</body></html>'; 174 return $this->output_css_plain; 175 } 176 177 /** 178 * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain 179 * 180 * @param bool $plain plain text or not 181 * @param string $default_media default @media to add to selectors without any @media 182 * 183 * @access private 184 * @version 2.0 185 */ 186 public function _print( $plain = false, $default_media = '' ) { 187 if ( $this->output_css && $this->output_css_plain ) { 188 return; 189 } 190 191 $output = ''; 192 if ( ! $this->parser->get_cfg( 'preserve_css' ) ) { 193 $this->_convert_raw_css( $default_media ); 194 } 195 196 $template = &$this->template; 197 198 if ( $plain ) { 199 $template = array_map( 'strip_tags', $template ); 200 } 201 202 if ( $this->parser->get_cfg( 'timestamp' ) ) { 203 array_unshift( $this->tokens, array( COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date( 'r' ) . ' ' ) ); 204 } 205 206 if ( ! empty( $this->charset ) ) { 207 $output .= $template[ 0 ] . '@charset ' . $template[ 5 ] . $this->charset . $template[ 6 ] . $template[ 13 ]; 208 } 209 210 if ( ! empty( $this->import ) ) { 211 for ( $i = 0, $size = count( $this->import ); $i < $size; $i++ ) { 212 if ( substr( $this->import[ $i ], 0, 4 ) === 'url(' && substr( $this->import[ $i ], -1, 1 ) === ')' ) { 213 $this->import[ $i ] = '"' . substr( $this->import[ $i ], 4, -1 ) . '"'; 214 $this->parser->log( 'Optimised @import : Removed "url("', 'Information' ); 215 } 216 else if ( ! preg_match( '/^".+"$/', $this->import[ $i ] ) ) { 217 // fixes a bug for @import ".." instead of the expected @import url("..") 218 // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error) 219 $this->import[ $i ] = '"' . $this->import[ $i ] . '"'; 220 } 221 222 $output .= $template[ 0 ] . '@import ' . $template[ 5 ] . $this->import[ $i ] . $template[ 6 ] . $template[ 13 ]; 223 } 224 } 225 226 if ( ! empty( $this->namespace ) ) { 227 if ( ( $p = strpos( $this->namespace, "url(" ) ) !== false && substr( $this->namespace, -1, 1 ) === ')' ) { 228 $this->namespace = substr_replace( $this->namespace, '"', $p, 4 ); 229 $this->namespace = substr( $this->namespace, 0, -1 ) . '"'; 230 $this->parser->log( 'Optimised @namespace : Removed "url("', 'Information' ); 231 } 232 $output .= $template[ 0 ] . '@namespace ' . $template[ 5 ] . $this->namespace . $template[ 6 ] . $template[ 13 ]; 233 } 234 235 $in_at_out = []; 236 $out = &$output; 237 $indent_level = 0; 238 239 foreach ( $this->tokens as $key => $token ) { 240 switch ( $token[ 0 ] ) { 241 case AT_START: 242 $out .= $template[ 0 ] . $this->_htmlsp( $token[ 1 ], $plain ) . $template[ 1 ]; 243 $indent_level++; 244 if ( ! isset( $in_at_out[ $indent_level ] ) ) { 245 $in_at_out[ $indent_level ] = ''; 246 } 247 $out = &$in_at_out[ $indent_level ]; 248 break; 249 250 case SEL_START: 251 if ( $this->parser->get_cfg( 'lowercase_s' ) ) { 252 $token[ 1 ] = strtolower( $token[ 1 ] ); 253 } 254 $out .= ( $token[ 1 ][ 0 ] !== '@' ) ? $template[ 2 ] . $this->_htmlsp( $token[ 1 ], $plain ) : $template[ 0 ] . $this->_htmlsp( $token[ 1 ], $plain ); 255 $out .= $template[ 3 ]; 256 break; 257 258 case PROPERTY: 259 if ( $this->parser->get_cfg( 'case_properties' ) === 2 ) { 260 $token[ 1 ] = strtoupper( $token[ 1 ] ); 261 } 262 elseif ( $this->parser->get_cfg( 'case_properties' ) === 1 ) { 263 $token[ 1 ] = strtolower( $token[ 1 ] ); 264 } 265 $out .= $template[ 4 ] . $this->_htmlsp( $token[ 1 ], $plain ) . ':' . $template[ 5 ]; 266 break; 267 268 case VALUE: 269 $out .= $this->_htmlsp( $token[ 1 ], $plain ); 270 if ( $this->_seeknocomment( $key, 1 ) == SEL_END && $this->parser->get_cfg( 'remove_last_;' ) ) { 271 $out .= str_replace( ';', '', $template[ 6 ] ); 272 } 273 else { 274 $out .= $template[ 6 ]; 275 } 276 break; 277 278 case SEL_END: 279 $out .= $template[ 7 ]; 280 if ( $this->_seeknocomment( $key, 1 ) != AT_END ) { 281 $out .= $template[ 8 ]; 282 } 283 break; 284 285 case AT_END: 286 if ( strlen( $template[ 10 ] ) ) { 287 // indent the bloc we are closing 288 $out = str_replace( "\n\n", "\r\n", $out ); // don't fill empty lines 289 $out = str_replace( "\n", "\n" . $template[ 10 ], $out ); 290 $out = str_replace( "\r\n", "\n\n", $out ); 291 } 292 if ( $indent_level > 1 ) { 293 $out = &$in_at_out[ $indent_level - 1 ]; 294 } 295 else { 296 $out = &$output; 297 } 298 $out .= $template[ 10 ] . $in_at_out[ $indent_level ]; 299 if ( $this->_seeknocomment( $key, 1 ) != AT_END ) { 300 $out .= $template[ 9 ]; 301 } 302 else { 303 $out .= rtrim( $template[ 9 ] ); 304 } 305 306 unset( $in_at_out[ $indent_level ] ); 307 $indent_level--; 308 break; 309 310 case IMPORTANT_COMMENT: 311 case COMMENT: 312 $out .= $template[ 11 ] . '/*' . $this->_htmlsp( $token[ 1 ], $plain ) . '*/' . $template[ 12 ]; 313 break; 314 } 315 } 316 317 $output = trim( $output ); 318 319 if ( ! $plain ) { 320 $this->output_css = $output; 321 $this->_print( true ); 322 } 323 else { 324 // If using spaces in the template, don't want these to appear in the plain output 325 $this->output_css_plain = str_replace( ' ', '', $output ); 326 } 327 } 328 329 /** 330 * Gets the next token type which is $move away from $key, excluding comments 331 * 332 * @param integer $key current position 333 * @param integer $move move this far 334 * 335 * @return mixed a token type 336 * @access private 337 * @version 1.0 338 */ 339 public function _seeknocomment( $key, $move ) { 340 $go = ( $move > 0 ) ? 1 : -1; 341 for ( $i = $key + 1; abs( $key - $i ) - 1 < abs( $move ); $i += $go ) { 342 if ( ! isset( $this->tokens[ $i ] ) ) { 343 return; 344 } 345 if ( $this->tokens[ $i ][ 0 ] == COMMENT ) { 346 $move += 1; 347 continue; 348 } 349 return $this->tokens[ $i ][ 0 ]; 350 } 351 } 352 353 /** 354 * Converts $this->css array to a raw array ($this->tokens) 355 * 356 * @param string $default_media default @media to add to selectors without any @media 357 * 358 * @access private 359 * @version 1.0 360 */ 361 public function _convert_raw_css( $default_media = '' ) { 362 $this->tokens = array(); 363 $sort_selectors = $this->parser->get_cfg( 'sort_selectors' ); 364 $sort_properties = $this->parser->get_cfg( 'sort_properties' ); 365 366 // important comment section ? 367 if ( isset( $this->css[ '!' ] ) ) { 368 $this->parser->_add_token( IMPORTANT_COMMENT, rtrim( $this->css[ '!' ] ), true ); 369 unset( $this->css[ '!' ] ); 370 } 371 372 foreach ( $this->css as $medium => $val ) { 373 if ( $sort_selectors ) { 374 ksort( $val ); 375 } 376 if ( intval( $medium ) < DEFAULT_AT ) { 377 // un medium vide (contenant @font-face ou autre @) ne produit aucun conteneur 378 if ( strlen( trim( $medium ) ) ) { 379 $parts_to_open = explode( '{', $medium ); 380 foreach ( $parts_to_open as $part ) { 381 $this->parser->_add_token( AT_START, $part, true ); 382 } 383 } 384 } 385 elseif ( $default_media ) { 386 $this->parser->_add_token( AT_START, $default_media, true ); 387 } 388 389 foreach ( $val as $selector => $vali ) { 390 if ( $sort_properties ) { 391 ksort( $vali ); 392 } 393 $this->parser->_add_token( SEL_START, $selector, true ); 394 395 $invalid = array( 396 '*' => array(), // IE7 hacks first 397 '_' => array(), // IE6 hacks 398 '/' => array(), // IE6 hacks 399 '-' => array() // IE6 hacks 400 ); 401 foreach ( $vali as $property => $valj ) { 402 if ( strncmp( $property, "//", 2 ) !== 0 ) { 403 $matches = array(); 404 if ( $sort_properties && preg_match( '/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches ) ) { 405 $invalid[ $matches[ 1 ] ][ $property ] = $valj; 406 } 407 else { 408 $this->parser->_add_token( PROPERTY, $property, true ); 409 $this->parser->_add_token( VALUE, $valj, true ); 410 } 411 } 412 } 413 foreach ( $invalid as $prefix => $props ) { 414 foreach ( $props as $property => $valj ) { 415 $this->parser->_add_token( PROPERTY, $property, true ); 416 $this->parser->_add_token( VALUE, $valj, true ); 417 } 418 } 419 $this->parser->_add_token( SEL_END, $selector, true ); 420 } 421 422 if ( intval( $medium ) < DEFAULT_AT ) { 423 // un medium vide (contenant @font-face ou autre @) ne produit aucun conteneur 424 if ( strlen( trim( $medium ) ) ) { 425 $parts_to_close = explode( '{', $medium ); 426 foreach ( array_reverse( $parts_to_close ) as $part ) { 427 $this->parser->_add_token( AT_END, $part, true ); 428 } 429 } 430 } 431 elseif ( $default_media ) { 432 $this->parser->_add_token( AT_END, $default_media, true ); 433 } 434 } 435 } 436 437 /** 438 * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner. 439 * 440 * @param string $string 441 * @param bool $plain 442 * 443 * @return string 444 * @see csstidy_print::_print() 445 * @access private 446 * @version 1.0 447 */ 448 public function _htmlsp( $string, $plain ) { 449 if ( ! $plain ) { 450 return htmlspecialchars( $string, ENT_QUOTES, 'utf-8' ); 451 } 452 return $string; 453 } 454 455 /** 456 * Get compression ratio 457 * @access public 458 * @return float 459 * @version 1.2 460 */ 461 public function get_ratio() { 462 if ( ! $this->output_css_plain ) { 463 $this->formatted(); 464 } 465 return round( ( strlen( $this->input_css ) - strlen( $this->output_css_plain ) ) / strlen( $this->input_css ), 3 ) * 100; 466 } 467 468 /** 469 * Get difference between the old and new code in bytes and prints the code if necessary. 470 * @access public 471 * @return string 472 * @version 1.1 473 */ 474 public function get_diff() { 475 if ( ! $this->output_css_plain ) { 476 $this->formatted(); 477 } 478 479 $diff = strlen( $this->output_css_plain ) - strlen( $this->input_css ); 480 481 if ( $diff > 0 ) { 482 return '+' . $diff; 483 } 484 elseif ( $diff == 0 ) { 485 return '+-' . $diff; 486 } 487 488 return $diff; 489 } 490 491 /** 492 * Get the size of either input or output CSS in KB 493 * 494 * @param string $loc default is "output" 495 * 496 * @access public 497 * @return integer 498 * @version 1.0 499 */ 500 public function size( $loc = 'output' ) { 501 if ( $loc === 'output' && ! $this->output_css ) { 502 $this->formatted(); 503 } 504 505 if ( $loc === 'input' ) { 506 return ( strlen( $this->input_css ) / 1000 ); 507 } 508 else { 509 return ( strlen( $this->output_css_plain ) / 1000 ); 510 } 511 } 512 513 } 142 break; 143 } 144 145 $output = $cssparsed = ''; 146 $this->output_css_plain = & $output; 147 148 $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"'; 149 $output .= ( $doctype === 'xhtml1.1') ? '>' : ' lang="' . $lang . '">'; 150 $output .= "\n<head>\n <title>$title</title>"; 151 152 if ($externalcss) { 153 $output .= "\n <style type=\"text/css\">\n"; 154 $cssparsed = file_get_contents('cssparsed.css'); 155 $output .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php 156 $output .= "\n</style>"; 157 } else { 158 $output .= "\n" . ' <link rel="stylesheet" type="text/css" href="cssparsed.css" />'; 159 } 160 $output .= "\n</head>\n<body><code id=\"copytext\">"; 161 $output .= $this->formatted(); 162 $output .= '</code>' . "\n" . '</body></html>'; 163 return $this->output_css_plain; 164 } 165 166 /** 167 * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain 168 * @param bool $plain plain text or not 169 * @param string $default_media default @media to add to selectors without any @media 170 * @access private 171 * @version 2.0 172 */ 173 public function _print($plain = false, $default_media='') { 174 if ($this->output_css && $this->output_css_plain) { 175 return; 176 } 177 178 $output = ''; 179 if (!$this->parser->get_cfg('preserve_css')) { 180 $this->_convert_raw_css($default_media); 181 } 182 183 $template = & $this->template; 184 185 if ($plain) { 186 $template = array_map('strip_tags', $template); 187 } 188 189 if ($this->parser->get_cfg('timestamp')) { 190 array_unshift($this->tokens, array(COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date('r') . ' ')); 191 } 192 193 if (!empty($this->charset)) { 194 $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6] . $template[13]; 195 } 196 197 if (!empty($this->import)) { 198 for ($i = 0, $size = count($this->import); $i < $size; $i++) { 199 if (substr($this->import[$i], 0, 4) === 'url(' && substr($this->import[$i], -1, 1) === ')') { 200 $this->import[$i] = '"' . substr($this->import[$i], 4, -1) . '"'; 201 $this->parser->log('Optimised @import : Removed "url("', 'Information'); 202 } 203 else if (!preg_match('/^".+"$/',$this->import[$i])) { 204 // fixes a bug for @import ".." instead of the expected @import url("..") 205 // If it comes in due to @import ".." the "" will be missing and the output will become @import .. (which is an error) 206 $this->import[$i] = '"' . $this->import[$i] . '"'; 207 } 208 209 $output .= $template[0] . '@import ' . $template[5] . $this->import[$i] . $template[6] . $template[13]; 210 } 211 } 212 213 if (!empty($this->namespace)) { 214 if (($p=strpos($this->namespace,"url("))!==false && substr($this->namespace, -1, 1) === ')') { 215 $this->namespace = substr_replace($this->namespace,'"',$p,4); 216 $this->namespace = substr($this->namespace, 0, -1) . '"'; 217 $this->parser->log('Optimised @namespace : Removed "url("', 'Information'); 218 } 219 $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6] . $template[13]; 220 } 221 222 $in_at_out = []; 223 $out = & $output; 224 $indent_level = 0; 225 226 foreach ($this->tokens as $key => $token) { 227 switch ($token[0]) { 228 case AT_START: 229 $out .= $template[0] . $this->_htmlsp($token[1], $plain) . $template[1]; 230 $indent_level++; 231 if (!isset($in_at_out[$indent_level])) { 232 $in_at_out[$indent_level] = ''; 233 } 234 $out = & $in_at_out[$indent_level]; 235 break; 236 237 case SEL_START: 238 if ($this->parser->get_cfg('lowercase_s')) 239 $token[1] = strtolower($token[1]); 240 $out .= ( $token[1][0] !== '@') ? $template[2] . $this->_htmlsp($token[1], $plain) : $template[0] . $this->_htmlsp($token[1], $plain); 241 $out .= $template[3]; 242 break; 243 244 case PROPERTY: 245 if ($this->parser->get_cfg('case_properties') === 2) { 246 $token[1] = strtoupper($token[1]); 247 } elseif ($this->parser->get_cfg('case_properties') === 1) { 248 $token[1] = strtolower($token[1]); 249 } 250 $out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5]; 251 break; 252 253 case VALUE: 254 $out .= $this->_htmlsp($token[1], $plain); 255 if ($this->_seeknocomment($key, 1) == SEL_END && $this->parser->get_cfg('remove_last_;')) { 256 $out .= str_replace(';', '', $template[6]); 257 } else { 258 $out .= $template[6]; 259 } 260 break; 261 262 case SEL_END: 263 $out .= $template[7]; 264 if ($this->_seeknocomment($key, 1) != AT_END) 265 $out .= $template[8]; 266 break; 267 268 case AT_END: 269 if (strlen($template[10])) { 270 // indent the bloc we are closing 271 $out = str_replace("\n\n", "\r\n", $out); // don't fill empty lines 272 $out = str_replace("\n", "\n" . $template[10], $out); 273 $out = str_replace("\r\n", "\n\n", $out); 274 } 275 if ($indent_level > 1) { 276 $out = & $in_at_out[$indent_level-1]; 277 } 278 else { 279 $out = & $output; 280 } 281 $out .= $template[10] . $in_at_out[$indent_level]; 282 if ($this->_seeknocomment($key, 1) != AT_END) { 283 $out .= $template[9]; 284 } 285 else { 286 $out .= rtrim($template[9]); 287 } 288 289 unset($in_at_out[$indent_level]); 290 $indent_level--; 291 break; 292 293 case IMPORTANT_COMMENT: 294 case COMMENT: 295 $out .= $template[11] . '/*' . $this->_htmlsp($token[1], $plain) . '*/' . $template[12]; 296 break; 297 } 298 } 299 300 $output = trim($output); 301 302 if (!$plain) { 303 $this->output_css = $output; 304 $this->_print(true); 305 } else { 306 // If using spaces in the template, don't want these to appear in the plain output 307 $this->output_css_plain = str_replace(' ', '', $output); 308 } 309 } 310 311 /** 312 * Gets the next token type which is $move away from $key, excluding comments 313 * @param integer $key current position 314 * @param integer $move move this far 315 * @return mixed a token type 316 * @access private 317 * @version 1.0 318 */ 319 public function _seeknocomment($key, $move) { 320 $go = ($move > 0) ? 1 : -1; 321 for ($i = $key + 1; abs($key - $i) - 1 < abs($move); $i += $go) { 322 if (!isset($this->tokens[$i])) { 323 return; 324 } 325 if ($this->tokens[$i][0] == COMMENT) { 326 $move += 1; 327 continue; 328 } 329 return $this->tokens[$i][0]; 330 } 331 } 332 333 /** 334 * Converts $this->css array to a raw array ($this->tokens) 335 * @param string $default_media default @media to add to selectors without any @media 336 * @access private 337 * @version 1.0 338 */ 339 public function _convert_raw_css($default_media='') { 340 $this->tokens = array(); 341 $sort_selectors = $this->parser->get_cfg('sort_selectors'); 342 $sort_properties = $this->parser->get_cfg('sort_properties'); 343 344 // important comment section ? 345 if (isset($this->css['!'])) { 346 $this->parser->_add_token(IMPORTANT_COMMENT, rtrim($this->css['!']), true); 347 unset($this->css['!']); 348 } 349 350 foreach ($this->css as $medium => $val) { 351 if ($sort_selectors) 352 ksort($val); 353 if (intval($medium) < DEFAULT_AT) { 354 // un medium vide (contenant @font-face ou autre @) ne produit aucun conteneur 355 if (strlen(trim($medium))) { 356 $parts_to_open = explode('{', $medium); 357 foreach ($parts_to_open as $part) { 358 $this->parser->_add_token(AT_START, $part, true); 359 } 360 } 361 } elseif ($default_media) { 362 $this->parser->_add_token(AT_START, $default_media, true); 363 } 364 365 foreach ($val as $selector => $vali) { 366 if ($sort_properties) 367 ksort($vali); 368 $this->parser->_add_token(SEL_START, $selector, true); 369 370 $invalid = array( 371 '*' => array(), // IE7 hacks first 372 '_' => array(), // IE6 hacks 373 '/' => array(), // IE6 hacks 374 '-' => array() // IE6 hacks 375 ); 376 foreach ($vali as $property => $valj) { 377 if (strncmp($property,"//",2)!==0) { 378 $matches = array(); 379 if ($sort_properties && preg_match('/^(\*|_|\/|-)(?!(ms|moz|o\b|xv|atsc|wap|khtml|webkit|ah|hp|ro|rim|tc)-)/', $property, $matches)) { 380 $invalid[$matches[1]][$property] = $valj; 381 } else { 382 $this->parser->_add_token(PROPERTY, $property, true); 383 $this->parser->_add_token(VALUE, $valj, true); 384 } 385 } 386 } 387 foreach ($invalid as $prefix => $props) { 388 foreach ($props as $property => $valj) { 389 $this->parser->_add_token(PROPERTY, $property, true); 390 $this->parser->_add_token(VALUE, $valj, true); 391 } 392 } 393 $this->parser->_add_token(SEL_END, $selector, true); 394 } 395 396 if (intval($medium) < DEFAULT_AT) { 397 // un medium vide (contenant @font-face ou autre @) ne produit aucun conteneur 398 if (strlen(trim($medium))) { 399 $parts_to_close = explode('{', $medium); 400 foreach (array_reverse($parts_to_close) as $part) { 401 $this->parser->_add_token(AT_END, $part, true); 402 } 403 } 404 } elseif ($default_media) { 405 $this->parser->_add_token(AT_END, $default_media, true); 406 } 407 } 408 } 409 410 /** 411 * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner. 412 * @param string $string 413 * @param bool $plain 414 * @return string 415 * @see csstidy_print::_print() 416 * @access private 417 * @version 1.0 418 */ 419 public function _htmlsp($string, $plain) { 420 if (!$plain) { 421 return htmlspecialchars($string, ENT_QUOTES, 'utf-8'); 422 } 423 return $string; 424 } 425 426 /** 427 * Get compression ratio 428 * @access public 429 * @return float 430 * @version 1.2 431 */ 432 public function get_ratio() { 433 if (!$this->output_css_plain) { 434 $this->formatted(); 435 } 436 return round((strlen($this->input_css) - strlen($this->output_css_plain)) / strlen($this->input_css), 3) * 100; 437 } 438 439 /** 440 * Get difference between the old and new code in bytes and prints the code if necessary. 441 * @access public 442 * @return string 443 * @version 1.1 444 */ 445 public function get_diff() { 446 if (!$this->output_css_plain) { 447 $this->formatted(); 448 } 449 450 $diff = strlen($this->output_css_plain) - strlen($this->input_css); 451 452 if ($diff > 0) { 453 return '+' . $diff; 454 } elseif ($diff == 0) { 455 return '+-' . $diff; 456 } 457 458 return $diff; 459 } 460 461 /** 462 * Get the size of either input or output CSS in KB 463 * @param string $loc default is "output" 464 * @access public 465 * @return integer 466 * @version 1.0 467 */ 468 public function size($loc = 'output') { 469 if ($loc === 'output' && !$this->output_css) { 470 $this->formatted(); 471 } 472 473 if ($loc === 'input') { 474 return (strlen($this->input_css) / 1000); 475 } else { 476 return (strlen($this->output_css_plain) / 1000); 477 } 478 } 479 480 } -
opt-out-for-google-analytics/trunk/lib/csstidy/data.inc.php
r2179559 r2787042 567 567 $data['csstidy']['all_properties']['word-wrap'] = 'CSS3.0'; 568 568 $data['csstidy']['all_properties']['z-index'] = 'CSS2.0,CSS2.1,CSS3.0'; 569 $data['csstidy']['all_properties']['--custom'] = 'CSS3.0'; 569 570 570 571 /** -
opt-out-for-google-analytics/trunk/readme.txt
r2664664 r2787042 3 3 Tags: google analytics, opt-out, dsgvo, gdpr, analytics 4 4 Requires at least: 3.5 5 Tested up to: 5.95 Tested up to: 6.0.2 6 6 Requires PHP: 7.0 7 Stable tag: 2. 07 Stable tag: 2.1 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 59 59 60 60 * [MonsterInsights Pro](https://www.monsterinsights.com/?utm_source=wordpressorg&utm_medium=opt-out-for-google-analytics) 61 * [ Google Analytics for WordPress by MonsterInsights](https://wordpress.org/plugins/google-analytics-for-wordpress/)61 * [MonsterInsights – Google Analytics Dashboard for WordPress (Website Stats Made Easy)](https://wordpress.org/plugins/google-analytics-for-wordpress/) 62 62 * [ExactMetrics – Google Analytics Dashboard for WordPress (Website Stats Plugin)](https://wordpress.org/plugins/google-analytics-dashboard-for-wp/) 63 63 * [Analytify – Google Analytics Dashboard Plugin For WordPress](https://wordpress.org/plugins/wp-analytify/) 64 64 * [GA Google Analytics](https://wordpress.org/plugins/ga-google-analytics/) 65 * [Site Kit by Google ](https://wordpress.org/plugins/google-site-kit/)65 * [Site Kit by Google – Analytics, Search Console, AdSense, Speed](https://wordpress.org/plugins/google-site-kit/) 66 66 67 67 **AUTOMATICALLY current privacy policy** … … 287 287 288 288 == Changelog == 289 290 = 2.1 = 291 * Updated: readme.txt 292 * Updated: Support for WordPress 6.0.2 293 * Updated: CSStidy library to v2.0.0 294 * Updated: Integration of all supported GA tracking plugins 289 295 290 296 = 2.0 = -
opt-out-for-google-analytics/trunk/templates/promotion.php
r2519134 r2787042 1 1 <?php if ( ! $popup ): ?> 2 <div class="gaoo-promotion-wrap">2 <div class="gaoo-promotion-wrap"> 3 3 <?php endif; ?> 4 4 5 5 <?php foreach ( $promo as $item ): ?> 6 6 7 <div class="gaoo-promotion-box"> 7 <?php 8 if ( GAOO_Promo::should_hide( $item[ 'hide_if_active' ] ) ): 9 continue; 10 endif; 11 ?> 8 12 9 <?php if ( $popup ): ?> 10 <h2 class="clearfix"><?php echo esc_html( $item[ 'title' ] ); ?> 11 <a href="<?php echo esc_url( add_query_arg( 'gaoo_promo', 1 ) ); ?>" title="<?php esc_attr_e( 'Close it for ever', 'opt-out-for-google-analytics' ); ?>">X</a></h2> 13 <div class="gaoo-promotion-box"> 14 15 <?php if ( $popup ): ?> 16 <h2 class="clearfix"><?php echo esc_html( $item[ 'title' ] ); ?> 17 <a href="<?php echo esc_url( add_query_arg( 'gaoo_promo', 1 ) ); ?>" title="<?php esc_attr_e( 'Close it for ever', 'opt-out-for-google-analytics' ); ?>">X</a> 18 </h2> 12 19 <?php endif; ?> 13 20 14 <div class="gaoo-promotion-box-body">21 <div class="gaoo-promotion-box-body"> 15 22 16 <?php if ( ! empty( $item[ 'link' ] ) ): ?>17 <a href="<?php echo esc_url( $item[ 'link' ] ); ?>" target="_blank" class="gaoo-promotion-link" title="<?php esc_attr_e( 'Opens in new tab', 'opt-out-for-google-analytics' ); ?>">18 <?php endif; ?>23 <?php if ( ! empty( $item[ 'link' ] ) ): ?> 24 <a href="<?php echo esc_url( $item[ 'link' ] ); ?>" target="_blank" class="gaoo-promotion-link" title="<?php esc_attr_e( 'Opens in new tab', 'opt-out-for-google-analytics' ); ?>"> 25 <?php endif; ?> 19 26 20 27 <?php if ( ! empty( $item[ 'image' ] ) ): ?> 21 <img src="<?php echo esc_url( $item[ 'image' ] ); ?>" alt="<?php echo empty( $item[ 'title' ] ) ? '' : esc_attr( $item[ 'title' ] ); ?>">28 <img src="<?php echo esc_url( $item[ 'image' ] ); ?>" alt="<?php echo empty( $item[ 'title' ] ) ? '' : esc_attr( $item[ 'title' ] ); ?>"> 22 29 <?php endif; ?> 23 30 24 31 <?php if ( ! empty( $item[ 'link' ] ) ): ?> 25 </a>32 </a> 26 33 <?php endif; ?> 27 34 28 35 <?php if ( ! empty( $item[ 'desc' ] ) ): ?> 29 <div class="gaoo-promotion-description">30 <?php echo wp_kses_post( $item[ 'desc' ] ); ?>31 </div>36 <div class="gaoo-promotion-description"> 37 <?php echo wp_kses_post( $item[ 'desc' ] ); ?> 38 </div> 32 39 <?php endif; ?> 33 </div>40 </div> 34 41 35 </div>42 </div> 36 43 37 44 <?php endforeach; ?> 38 45 39 46 <?php if ( ! $popup ): ?> 40 </div>47 </div> 41 48 <?php endif; ?>
Note: See TracChangeset
for help on using the changeset viewer.