line_ending = "\n"; $this->tab = "\t"; } } /** * Add a new CSS rule to the array. * * Accepts data to eventually be turned into CSS. Usage: * * Customizer_Library_Styles()->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.0.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 CSS syntax * * @since 1.0.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.0.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.0.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 `Customizer_Library_Styles()->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; } } endif; if (!function_exists('customizer_library_styles')) : /** * Return the one Customizer_Library_Styles object. * * @since 1.0.0. * * @return Customizer_Library_Styles The Customizer_Library_Styles object. */ function customizer_library_styles() { return Customizer_Library_Styles::instance(); } endif; add_action('init', 'customizer_library_styles', 1);