line_ending = "\n"; $this->tab = "\t"; } } /** * Add a new CSS rule to the array. * * Accepts data to eventually be turned into CSS. Usage: * * bizznis_get_css()->add( array( * 'selectors' => array( '.site-header-main' ), * 'declarations' => array( * 'background-color' => $header_background_color * ), * 'media' => 'print', * ) ); * * Selectors represent the CSS selectors; declarations are the CSS properties and values with keys being properties * and values being values. 'media' can also be declared to specify the media query. * * Note that data *must* be sanitized when adding to the data array. Because every piece of CSS data has special * sanitization concerns, it must be handled at the time of addition, not at the time of output. The theme handles * this in the the other helper files, i.e., the data is already sanitized when `add()` is called. * * @since 1.1.0. * * @param array $data The selectors and properties to add to the CSS. * @return void */ public function add( $data ) { if ( ! isset( $data['selectors'] ) || ! isset( $data['declarations'] ) ) { return; } $entry = array(); # Sanitize selectors $entry['selectors'] = array_map( 'trim', (array) $data['selectors'] ); $entry['selectors'] = array_unique( $entry['selectors'] ); # Sanitize declarations $entry['declarations'] = array_map( 'trim', (array) $data['declarations'] ); # Check for media query if ( isset( $data['media'] ) ) { $media = $data['media']; } else { $media = 'all'; } # Create new media query if it doesn't exist yet if ( ! isset( $this->data[ $media ] ) || ! is_array( $this->data[ $media ] ) ) { $this->data[ $media ] = array(); } # Look for matching selector sets $match = false; foreach ( $this->data[ $media ] as $key => $rule ) { $diff1 = array_diff( $rule['selectors'], $entry['selectors'] ); $diff2 = array_diff( $entry['selectors'], $rule['selectors'] ); if ( empty( $diff1 ) && empty( $diff2 ) ) { $match = $key; break; } } # No matching selector set, add a new entry if ( false === $match ) { $this->data[ $media ][] = $entry; } # Yes, matching selector set, merge declarations else { $this->data[ $media ][ $match ]['declarations'] = array_merge( $this->data[ $media ][ $match ]['declarations'], $entry['declarations'] ); } } /** * Compile the data array into standard CSS syntax * * @since 1.1.0. * * @return string The CSS that is built from the data. */ public function build() { if ( empty( $this->data ) ) { return ''; } $n = $this->line_ending; # Make sure the 'all' array is first if ( isset( $this->data['all'] ) && count( $this->data ) > 1 ) { $all = array ( 'all' => $this->data['all'] ); unset( $this->data['all'] ); $this->data = array_merge( $all, $this->data); } $output = ''; foreach ( $this->data as $query => $ruleset ) { $t = ''; if ( 'all' !== $query ) { $output .= "\n@media " . $query . '{' . $n; $t = $this->tab; } # Build each rule foreach ( $ruleset as $rule ) { $output .= $this->parse_selectors( $rule['selectors'], $t ) . '{' . $n; $output .= $this->parse_declarations( $rule['declarations'], $t ); $output .= $t . '}' . $n; } if ( 'all' !== $query ) { $output .= '}' . $n; } } return $output; } /** * Compile the selectors in a rule into a string. * * @since 1.1.0. * * @param array $selectors Selectors to combine into single selector. * @param string $tab Tab character. * @return string Results of the selector combination. */ private function parse_selectors( $selectors, $tab = '' ) { /** * Note that these selectors are hardcoded in the code base. They are never the result of user input and can * thus be trusted to be sane. */ $n = $this->line_ending; $output = $tab . implode( ",{$n}{$tab}", $selectors ); return $output; } /** * Compile the declarations in a rule into a string. * * @since 1.1.0. * * @param array $declarations Declarations for a selector. * @param string $tab Tab character. * @return string The combines declarations. */ private function parse_declarations( $declarations, $tab = '' ) { $n = $this->line_ending; $t = $this->tab . $tab; $output = ''; /** * Note that when this output is prepared, it is not escaped, sanitized or otherwise altered. The sanitization * routines are implemented when the developer calls `bizznis_get_css->add()`. Because every property value has * special sanitization needs, it is handled at that point. */ foreach ( $declarations as $property => $value ) { // Exception for px/rem font size if ( 'font-size-px' === $property || 'font-size-rem' === $property ) { $property = 'font-size'; } $output .= "{$t}{$property}:{$value};$n"; } return $output; } } /** * Return the one Bizznis_CSS object. * * @since 1.1.0. * * @return Bizznis_CSS The one Bizznis_CSS object. */ add_action( 'init', 'bizznis_get_css', 1 ); function bizznis_get_css() { return Bizznis_CSS::instance(); } /** * Make sure the 'bizznis_css' action only runs once. * * @since 1.1.1. * * @return void */ add_action( 'admin_init', 'bizznis_add_customizations' ); function bizznis_add_customizations() { do_action( 'bizznis_css' ); } /** * Generates the style tag and CSS needed for the theme options. * * By using the "bizznis_css" filter, different components can print CSS in the header. It is organized this way to * ensure that there is only one "style" tag and not a proliferation of them. * * @since 1.1.0. * * @return void */ add_action( 'wp_head', 'bizznis_display_customizations', 11 ); function bizznis_display_customizations() { do_action( 'bizznis_css' ); # Echo the rules $css = bizznis_get_css()->build(); if ( ! empty( $css ) ) { echo "\n\n\n\n"; } } /** * Generates the theme option CSS as an Ajax response * * @since 1.1.0. * * @return void */ add_action( 'wp_ajax_bizznis-css', 'bizznis_ajax_display_customizations' ); function bizznis_ajax_display_customizations() { # Make sure this is an Ajax request if ( ! defined( 'DOING_AJAX' ) || true !== DOING_AJAX ) { return; } # Set the content type header( "Content-Type: text/css" ); # Echo the rules echo bizznis_get_css()->build(); # End the Ajax response die(); } /** * Make sure theme option CSS is added to TinyMCE last, to override other styles. * * @since 1.1.0. * * @param string $stylesheets List of stylesheets added to TinyMCE. * @return string Modified list of stylesheets. */ add_filter( 'mce_css', 'bizznis_mce_css', 99 ); function bizznis_mce_css( $stylesheets ) { if ( bizznis_get_css()->build() ) { $stylesheets .= ',' . add_query_arg( 'action', 'bizznis-css', admin_url( 'admin-ajax.php' ) ); } return $stylesheets; }