Plugin Directory

Changeset 2787042


Ignore:
Timestamp:
09/19/2022 02:38:08 PM (4 years ago)
Author:
schweizersolutions
Message:

2.1

  • Updated: readme.txt
  • Updated: Support for WordPress 6.0.2
  • Updated: CSStidy library to v2.0.0
  • Updated: Integration of all supported GA tracking plugins
Location:
opt-out-for-google-analytics/trunk
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • opt-out-for-google-analytics/trunk/constants.php

    r2664664 r2787042  
    88    defined( 'GAOO_PLUGIN_URL' ) || define( 'GAOO_PLUGIN_URL', WP_PLUGIN_URL . DIRECTORY_SEPARATOR . GAOO_PLUGIN_NAME );
    99    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' );
    1111    defined( 'GAOO_SHORTCODE' ) || define( 'GAOO_SHORTCODE', '[ga_optout]' );
    1212    defined( 'GAOO_CAPABILITY' ) || define( 'GAOO_CAPABILITY', 'manage_options' );
  • opt-out-for-google-analytics/trunk/ga-opt-out.php

    r2664664 r2787042  
    44     * Plugin URI: https://www.schweizersolutions.com/?utm_source=wordpress&utm_medium=plugin&utm_campaign=plugin_uri
    55     * 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.0
     6     * Version: 2.1
    77     * Author: Schweizer Solutions GmbH
    88     * Author URI: https://www.schweizersolutions.com/?utm_source=wordpress&utm_medium=plugin&utm_campaign=author_uri
     
    8181    add_action( 'init', array( $gaoo, 'init' ) );
    8282    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  
    55
    66    class GAOO_Admin {
     7
    78        private $messages;
    89        private $csstidy;
     
    1415         */
    1516        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 );
    2567
    2668            $this->plugin    = GAOO_PLUGIN_NAME . DIRECTORY_SEPARATOR . 'ga-opt-out.php';
     
    3375
    3476        /**
     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        /**
    3589         * Load TinyMCE filters and hooks
    3690         */
     
    4599            }
    46100
    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            ) );
    49109        }
    50110
     
    64124
    65125            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                }
    72135            }
    73136
     
    86149        public function add_dashboard_widget() {
    87150            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                ) );
    89155            }
    90156        }
     
    134200         */
    135201        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            ) );
    137206        }
    138207
     
    140209         * Customize the action links.
    141210         *
    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.
    144213         *
    145214         * @return array Action links.
     
    156225         * Plugin row meta links
    157226         *
     227         * @param array $links already defined meta links.
     228         * @param string $file plugin file path and name being processed.
     229         *
     230         * @return array $input
    158231         * @since 1.1
    159232         *
    160          * @param array  $links already defined meta links.
    161          * @param string $file  plugin file path and name being processed.
    162          *
    163          * @return array $input
    164233         */
    165234        function plugin_row_meta( $links, $file ) {
    166235            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>";
    174237
    175238                if ( ! empty( $this->promotion ) ) {
     
    206269
    207270            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 );
    209275            }
    210276        }
     
    244310            $ga_plugins = array(
    245311                'monsterinsights' => array(
    246                     'label'        => 'Google Analytics for WordPress by MonsterInsights',
     312                    'label'        => 'MonsterInsights – Google Analytics Dashboard for WordPress (Website Stats Made Easy)',
    247313                    'url_install'  => admin_url( 'plugin-install.php?tab=search&s=Google+Analytics+MonsterInsights' ),
    248314                    'url_activate' => admin_url( 'plugins.php?plugin_status=inactive&s=MonsterInsights' ),
     
    274340                ),
    275341                'sitekit'         => array(
    276                     'label'        => 'Site Kit by Google',
     342                    'label'        => 'Site Kit by Google – Analytics, Search Console, AdSense, Speed',
    277343                    'url_install'  => admin_url( 'plugin-install.php?tab=search&s=Google+site+kit' ),
    278344                    'url_activate' => admin_url( 'plugins.php?plugin_status=inactive&s=site+kit' ),
  • opt-out-for-google-analytics/trunk/inc/promo.class.php

    r2519134 r2787042  
    88     */
    99    class GAOO_Promo {
     10
    1011        /**
    1112         * @var string $option_key_off Option name to store status
     
    1415
    1516        /**
    16          * @var string $transient_key_cache TRansient name for API cached data
     17         * @var string $transient_key_cache Transient name for API cached data
    1718         */
    1819        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';
    1925
    2026        /**
     
    5157
    5258        /**
     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        /**
    53108         * Render the promotion box.
    54109         *
    55110         * @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)
    58113         *
    59114         * @return string HTML code on success, otherwise empty string.
     
    151206            }
    152207
    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' ] );
    156215            } );
    157216
     217            $data = GAOO_Utils::get_array_columns( $data, array(
     218                'link',
     219                'link_text'
     220            ) );
     221
    158222            return empty( $data ) ? null : $data;
    159223        }
     224
    160225    }
  • opt-out-for-google-analytics/trunk/inc/public.class.php

    r2664664 r2787042  
    55
    66    class GAOO_Public {
     7
    78        private $csstidy;
    89
     
    1112         */
    1213        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 );
    1518
    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            ) );
    1728
    1829            if ( has_filter( 'widget_text', 'do_shortcode' ) !== false ) {
     
    8495
    8596            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' ] ) );
    8798            }
    8899
  • opt-out-for-google-analytics/trunk/lib/csstidy/class.csstidy.php

    r2518394 r2787042  
    11<?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 */
     39define('AT_START',         1);
     40define('AT_END',           2);
     41define('SEL_START',        3);
     42define('SEL_END',          4);
     43define('PROPERTY',         5);
     44define('VALUE',            6);
     45define('COMMENT',          7);
     46define('IMPORTANT_COMMENT',8);
     47define('DEFAULT_AT',      41);
     48
     49/**
     50 * Contains a class for printing CSS code
     51 *
     52 * @version 1.1.0
     53 */
     54require('class.csstidy_print.php');
     55
     56/**
     57 * Contains a class for optimising CSS code
     58 *
     59 * @version 1.0
     60 */
     61require('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 */
     75class 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        /*
    277275            1 common shorthands optimization
    278276            2 + font property optimization
    279277            3 + background property optimization
    280278         */
    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 gzip
     279        $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
    287285         * but can cause trouble in case of overiding same propertie or using hack
    288286         */
    289             $this->settings[ 'sort_properties' ] = false;
    290             /*
     287        $this->settings['sort_properties'] = false;
     288        /*
    291289            1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
    292290            2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
    293291            preserve order by default cause it can break functionnality
    294292         */
    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  
    11<?php
    22
    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 */
     43class 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] = '';
    85425                        }
    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  
    11<?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 */
     42class 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"
    146136            "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"
    151141                "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( '&#160;', '', $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('&#160;', '', $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  
    567567$data['csstidy']['all_properties']['word-wrap'] = 'CSS3.0';
    568568$data['csstidy']['all_properties']['z-index'] = 'CSS2.0,CSS2.1,CSS3.0';
     569$data['csstidy']['all_properties']['--custom'] = 'CSS3.0';
    569570
    570571/**
  • opt-out-for-google-analytics/trunk/readme.txt

    r2664664 r2787042  
    33Tags: google analytics, opt-out, dsgvo, gdpr, analytics
    44Requires at least: 3.5
    5 Tested up to: 5.9
     5Tested up to: 6.0.2
    66Requires PHP: 7.0
    7 Stable tag: 2.0
     7Stable tag: 2.1
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    5959
    6060* [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/)
    6262* [ExactMetrics – Google Analytics Dashboard for WordPress (Website Stats Plugin)](https://wordpress.org/plugins/google-analytics-dashboard-for-wp/)
    6363* [Analytify – Google Analytics Dashboard Plugin For WordPress](https://wordpress.org/plugins/wp-analytify/)
    6464* [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/)
    6666
    6767**AUTOMATICALLY current privacy policy**
     
    287287
    288288== 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
    289295
    290296= 2.0 =
  • opt-out-for-google-analytics/trunk/templates/promotion.php

    r2519134 r2787042  
    11<?php if ( ! $popup ): ?>
    2     <div class="gaoo-promotion-wrap">
     2    <div class="gaoo-promotion-wrap">
    33<?php endif; ?>
    44
    55<?php foreach ( $promo as $item ): ?>
    66
    7     <div class="gaoo-promotion-box">
     7    <?php
     8    if ( GAOO_Promo::should_hide( $item[ 'hide_if_active' ] ) ):
     9        continue;
     10    endif;
     11    ?>
    812
    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>
    1219        <?php endif; ?>
    1320
    14         <div class="gaoo-promotion-box-body">
     21        <div class="gaoo-promotion-box-body">
    1522
    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; ?>
    1926
    2027                <?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' ] ); ?>">
    2229                <?php endif; ?>
    2330
    2431                <?php if ( ! empty( $item[ 'link' ] ) ): ?>
    25                 </a>
     32            </a>
    2633        <?php endif; ?>
    2734
    2835            <?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>
    3239            <?php endif; ?>
    33         </div>
     40        </div>
    3441
    35     </div>
     42    </div>
    3643
    3744<?php endforeach; ?>
    3845
    3946<?php if ( ! $popup ): ?>
    40     </div>
     47    </div>
    4148<?php endif; ?>
Note: See TracChangeset for help on using the changeset viewer.