summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/modules/custom-css')
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php1246
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php46
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php938
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php410
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.css119
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparse.css118
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparse.min.css2
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.css30
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparsed.css29
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/cssparsed.min.css2
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php102
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/data.inc.php693
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/lang.inc.php308
-rw-r--r--plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl10
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css-4.7.php1165
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css.php1866
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/blank.css1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.css262
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/codemirror.css262
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/codemirror.min.css2
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.css33
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/css-editor.css32
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/css-editor.min.css2
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/customizer-control.css150
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/rtl/codemirror-rtl.css260
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.css7
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.css6
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.min.css2
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/codemirror.min.js11
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css-preview.js42
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js85
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.js192
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/css-editor.js91
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/js/use-codemirror.js52
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors.php58
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php3768
-rw-r--r--plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php4383
-rw-r--r--plugins/jetpack/modules/custom-css/migrate-to-core.php243
43 files changed, 17033 insertions, 0 deletions
diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php
new file mode 100644
index 00000000..e92669da
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy.php
@@ -0,0 +1,1246 @@
+<?php
+/**
+ * CSSTidy - CSS Parser and Optimiser
+ *
+ * CSS Parser class
+ *
+ * Copyright 2005, 2006, 2007 Florian Schmitz
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2007
+ * @author Brett Zamir (brettz9 at yahoo dot com) 2007
+ * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
+ * @author Cedric Morin (cedric at yterium dot com) 2010
+ */
+/**
+ * Defines ctype functions if required
+ *
+ * @version 1.0
+ */
+require_once( dirname( __FILE__ ) . '/class.csstidy_ctype.php' );
+
+/**
+ * Various CSS data needed for correct optimisations etc.
+ *
+ * @version 1.3
+ */
+require( dirname( __FILE__ ) . '/data.inc.php' );
+
+/**
+ * Contains a class for printing CSS code
+ *
+ * @version 1.0
+ */
+require( dirname( __FILE__ ) . '/class.csstidy_print.php' );
+
+/**
+ * Contains a class for optimising CSS code
+ *
+ * @version 1.0
+ */
+require( dirname( __FILE__ ) . '/class.csstidy_optimise.php' );
+
+/**
+ * CSS Parser class
+ *
+
+ * This class represents a CSS parser which reads CSS code and saves it in an array.
+ * In opposite to most other CSS parsers, it does not use regular expressions and
+ * thus has full CSS2 support and a higher reliability.
+ * Additional to that it applies some optimisations and fixes to the CSS code.
+ * An online version should be available here: http://cdburnerxp.se/cssparse/css_optimiser.php
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2006
+ * @version 1.3.1
+ */
+class csstidy {
+
+ /**
+ * Saves the parsed CSS. This array is empty if preserve_css is on.
+ * @var array
+ * @access public
+ */
+ public $css = array();
+ /**
+ * Saves the parsed CSS (raw)
+ * @var array
+ * @access private
+ */
+ public $tokens = array();
+ /**
+ * Printer class
+ * @see csstidy_print
+ * @var object
+ * @access public
+ */
+ public $print;
+ /**
+ * Optimiser class
+ * @see csstidy_optimise
+ * @var object
+ * @access private
+ */
+ public $optimise;
+ /**
+ * Saves the CSS charset (@charset)
+ * @var string
+ * @access private
+ */
+ public $charset = '';
+ /**
+ * Saves all @import URLs
+ * @var array
+ * @access private
+ */
+ public $import = array();
+ /**
+ * Saves the namespace
+ * @var string
+ * @access private
+ */
+ public $namespace = '';
+ /**
+ * Contains the version of csstidy
+ * @var string
+ * @access private
+ */
+ public $version = '1.3';
+ /**
+ * Stores the settings
+ * @var array
+ * @access private
+ */
+ public $settings = array();
+ /**
+ * Saves the parser-status.
+ *
+ * Possible values:
+ * - is = in selector
+ * - ip = in property
+ * - iv = in value
+ * - instr = in string (started at " or ' or ( )
+ * - ic = in comment (ignore everything)
+ * - at = in @-block
+ *
+ * @var string
+ * @access private
+ */
+ public $status = 'is';
+ /**
+ * Saves the current at rule (@media)
+ * @var string
+ * @access private
+ */
+ public $at = '';
+ /**
+ * Saves the current selector
+ * @var string
+ * @access private
+ */
+ public $selector = '';
+ /**
+ * Saves the current property
+ * @var string
+ * @access private
+ */
+ public $property = '';
+ /**
+ * Saves the position of , in selectors
+ * @var array
+ * @access private
+ */
+ public $sel_separate = array();
+ /**
+ * Saves the current value
+ * @var string
+ * @access private
+ */
+ public $value = '';
+ /**
+ * Saves the current sub-value
+ *
+ * Example for a subvalue:
+ * background:url(foo.png) red no-repeat;
+ * "url(foo.png)", "red", and "no-repeat" are subvalues,
+ * separated by whitespace
+ * @var string
+ * @access private
+ */
+ public $sub_value = '';
+ /**
+ * Array which saves all subvalues for a property.
+ * @var array
+ * @see sub_value
+ * @access private
+ */
+ public $sub_value_arr = array();
+ /**
+ * Saves the stack of characters that opened the current strings
+ * @var array
+ * @access private
+ */
+ public $str_char = array();
+ public $cur_string = array();
+ /**
+ * Status from which the parser switched to ic or instr
+ * @var array
+ * @access private
+ */
+ public $from = array();
+ /**
+ /**
+ * =true if in invalid at-rule
+ * @var bool
+ * @access private
+ */
+ public $invalid_at = false;
+ /**
+ * =true if something has been added to the current selector
+ * @var bool
+ * @access private
+ */
+ public $added = false;
+ /**
+ * Array which saves the message log
+ * @var array
+ * @access private
+ */
+ public $log = array();
+ /**
+ * Saves the line number
+ * @var integer
+ * @access private
+ */
+ public $line = 1;
+ /**
+ * Marks if we need to leave quotes for a string
+ * @var array
+ * @access private
+ */
+ public $quoted_string = array();
+
+ /**
+ * List of tokens
+ * @var string
+ */
+ public $tokens_list = "";
+
+ /**
+ * Loads standard template and sets default settings
+ * @access private
+ * @version 1.3
+ */
+ function __construct() {
+ $this->settings['remove_bslash'] = true;
+ $this->settings['compress_colors'] = true;
+ $this->settings['compress_font-weight'] = true;
+ $this->settings['lowercase_s'] = false;
+ /*
+ 1 common shorthands optimization
+ 2 + font property optimization
+ 3 + background property optimization
+ */
+ $this->settings['optimise_shorthands'] = 1;
+ $this->settings['remove_last_;'] = true;
+ /* rewrite all properties with low case, better for later gzip OK, safe*/
+ $this->settings['case_properties'] = 1;
+ /* sort properties in alpabetic order, better for later gzip
+ * but can cause trouble in case of overiding same propertie or using hack
+ */
+ $this->settings['sort_properties'] = false;
+ /*
+ 1, 3, 5, etc -- enable sorting selectors inside @media: a{}b{}c{}
+ 2, 5, 8, etc -- enable sorting selectors inside one CSS declaration: a,b,c{}
+ preserve order by default cause it can break functionnality
+ */
+ $this->settings['sort_selectors'] = 0;
+ /* is dangeroues to be used: CSS is broken sometimes */
+ $this->settings['merge_selectors'] = 0;
+ /* preserve or not browser hacks */
+ $this->settings['discard_invalid_selectors'] = false;
+ $this->settings['discard_invalid_properties'] = false;
+ $this->settings['css_level'] = 'CSS2.1';
+ $this->settings['preserve_css'] = false;
+ $this->settings['timestamp'] = false;
+ $this->settings['template'] = ''; // say that propertie exist
+ $this->set_cfg('template','default'); // call load_template
+ $this->optimise = new csstidy_optimise($this);
+
+ $this->tokens_list = & $GLOBALS['csstidy']['tokens'];
+ }
+
+ function csstidy() {
+ $this->__construct();
+ }
+
+ /**
+ * Get the value of a setting.
+ * @param string $setting
+ * @access public
+ * @return mixed
+ * @version 1.0
+ */
+ function get_cfg($setting) {
+ if (isset($this->settings[$setting])) {
+ return $this->settings[$setting];
+ }
+ return false;
+ }
+
+ /**
+ * Load a template
+ * @param string $template used by set_cfg to load a template via a configuration setting
+ * @access private
+ * @version 1.4
+ */
+ function _load_template($template) {
+ switch ($template) {
+ case 'default':
+ $this->load_template('default');
+ break;
+
+ case 'highest':
+ $this->load_template('highest_compression');
+ break;
+
+ case 'high':
+ $this->load_template('high_compression');
+ break;
+
+ case 'low':
+ $this->load_template('low_compression');
+ break;
+
+ default:
+ $this->load_template($template);
+ break;
+ }
+ }
+
+ /**
+ * Set the value of a setting.
+ * @param string $setting
+ * @param mixed $value
+ * @access public
+ * @return bool
+ * @version 1.0
+ */
+ function set_cfg($setting, $value=null) {
+ if (is_array($setting) && $value === null) {
+ foreach ($setting as $setprop => $setval) {
+ $this->settings[$setprop] = $setval;
+ }
+ if (array_key_exists('template', $setting)) {
+ $this->_load_template($this->settings['template']);
+ }
+ return true;
+ } else if (isset($this->settings[$setting]) && $value !== '') {
+ $this->settings[$setting] = $value;
+ if ($setting === 'template') {
+ $this->_load_template($this->settings['template']);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds a token to $this->tokens
+ * @param mixed $type
+ * @param string $data
+ * @param bool $do add a token even if preserve_css is off
+ * @access private
+ * @version 1.0
+ */
+ function _add_token($type, $data, $do = false) {
+ if ($this->get_cfg('preserve_css') || $do) {
+ $this->tokens[] = array($type, ($type == COMMENT) ? $data : trim($data));
+ }
+ }
+
+ /**
+ * Add a message to the message log
+ * @param string $message
+ * @param string $type
+ * @param integer $line
+ * @access private
+ * @version 1.0
+ */
+ function log($message, $type, $line = -1) {
+ if ($line === -1) {
+ $line = $this->line;
+ }
+ $line = intval($line);
+ $add = array('m' => $message, 't' => $type);
+ if (!isset($this->log[$line]) || !in_array($add, $this->log[$line])) {
+ $this->log[$line][] = $add;
+ }
+ }
+
+ /**
+ * Parse unicode notations and find a replacement character
+ * @param string $string
+ * @param integer $i
+ * @access private
+ * @return string
+ * @version 1.2
+ */
+ function _unicode(&$string, &$i) {
+ ++$i;
+ $add = '';
+ $replaced = false;
+
+ while ($i < strlen($string) && (ctype_xdigit($string{$i}) || ctype_space($string{$i})) && strlen($add) < 6) {
+ $add .= $string{$i};
+
+ if (ctype_space($string{$i})) {
+ break;
+ }
+ $i++;
+ }
+
+ if (hexdec($add) > 47 && hexdec($add) < 58 || hexdec($add) > 64 && hexdec($add) < 91 || hexdec($add) > 96 && hexdec($add) < 123) {
+ $this->log('Replaced unicode notation: Changed \\' . $add . ' to ' . chr(hexdec($add)), 'Information');
+ $add = chr(hexdec($add));
+ $replaced = true;
+ } else {
+ $add = trim('\\' . $add);
+ }
+
+ if (@ctype_xdigit($string{$i + 1}) && ctype_space($string{$i})
+ && !$replaced || !ctype_space($string{$i})) {
+ $i--;
+ }
+
+ if ($add !== '\\' || !$this->get_cfg('remove_bslash') || strpos($this->tokens_list, $string{$i + 1}) !== false) {
+ return $add;
+ }
+
+ if ($add === '\\') {
+ $this->log('Removed unnecessary backslash', 'Information');
+ }
+ return '';
+ }
+
+ /**
+ * Write formatted output to a file
+ * @param string $filename
+ * @param string $doctype when printing formatted, is a shorthand for the document type
+ * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
+ * @param string $title when printing formatted, is the title to be added in the head of the document
+ * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
+ * @access public
+ * @version 1.4
+ */
+ function write_page($filename, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') {
+ $this->write($filename, true);
+ }
+
+ /**
+ * Write plain output to a file
+ * @param string $filename
+ * @param bool $formatted whether to print formatted or not
+ * @param string $doctype when printing formatted, is a shorthand for the document type
+ * @param bool $externalcss when printing formatted, indicates whether styles to be attached internally or as an external stylesheet
+ * @param string $title when printing formatted, is the title to be added in the head of the document
+ * @param string $lang when printing formatted, gives a two-letter language code to be added to the output
+ * @param bool $pre_code whether to add pre and code tags around the code (for light HTML formatted templates)
+ * @access public
+ * @version 1.4
+ */
+ function write($filename, $formatted=false, $doctype='xhtml1.1', $externalcss=true, $title='', $lang='en', $pre_code=true) {
+ $filename .= ( $formatted) ? '.xhtml' : '.css';
+
+ if (!is_dir('temp')) {
+ $madedir = mkdir('temp');
+ if (!$madedir) {
+ print 'Could not make directory "temp" in ' . dirname(__FILE__);
+ exit;
+ }
+ }
+ $handle = fopen('temp/' . $filename, 'w');
+ if ($handle) {
+ if (!$formatted) {
+ fwrite($handle, $this->print->plain());
+ } else {
+ fwrite($handle, $this->print->formatted_page($doctype, $externalcss, $title, $lang, $pre_code));
+ }
+ }
+ fclose($handle);
+ }
+
+ /**
+ * Loads a new template
+ * @param string $content either filename (if $from_file == true), content of a template file, "high_compression", "highest_compression", "low_compression", or "default"
+ * @param bool $from_file uses $content as filename if true
+ * @access public
+ * @version 1.1
+ * @see http://csstidy.sourceforge.net/templates.php
+ */
+ function load_template($content, $from_file=true) {
+ $predefined_templates = & $GLOBALS['csstidy']['predefined_templates'];
+ if ($content === 'high_compression' || $content === 'default' || $content === 'highest_compression' || $content === 'low_compression') {
+ $this->template = $predefined_templates[$content];
+ return;
+ }
+
+
+ if ($from_file) {
+ $content = strip_tags(file_get_contents($content), '<span>');
+ }
+ $content = str_replace("\r\n", "\n", $content); // Unify newlines (because the output also only uses \n)
+ $template = explode('|', $content);
+
+ for ($i = 0; $i < count($template); $i++) {
+ $this->template[$i] = $template[$i];
+ }
+ }
+
+ /**
+ * Starts parsing from URL
+ * @param string $url
+ * @access public
+ * @version 1.0
+ */
+ function parse_from_url($url) {
+ return $this->parse(@file_get_contents($url));
+ }
+
+ /**
+ * Checks if there is a token at the current position
+ * @param string $string
+ * @param integer $i
+ * @access public
+ * @version 1.11
+ */
+ function is_token(&$string, $i) {
+ return (strpos($this->tokens_list, $string{$i}) !== false && !csstidy::escaped($string, $i));
+ }
+
+ /**
+ * Parses CSS in $string. The code is saved as array in $this->css
+ * @param string $string the CSS code
+ * @access public
+ * @return bool
+ * @version 1.1
+ */
+ function parse($string) {
+ // Temporarily set locale to en_US in order to handle floats properly
+ $old = @setlocale(LC_ALL, 0);
+ @setlocale(LC_ALL, 'C');
+
+ // PHP bug? Settings need to be refreshed in PHP4
+ $this->print = new csstidy_print($this);
+ //$this->optimise = new csstidy_optimise($this);
+
+ $all_properties = & $GLOBALS['csstidy']['all_properties'];
+ $at_rules = & $GLOBALS['csstidy']['at_rules'];
+ $quoted_string_properties = & $GLOBALS['csstidy']['quoted_string_properties'];
+
+ $this->css = array();
+ $this->print->input_css = $string;
+ $string = str_replace("\r\n", "\n", $string) . ' ';
+ $cur_comment = '';
+
+ for ($i = 0, $size = strlen($string); $i < $size; $i++) {
+ if ($string{$i} === "\n" || $string{$i} === "\r") {
+ ++$this->line;
+ }
+
+ switch ($this->status) {
+ /* Case in at-block */
+ case 'at':
+ if (csstidy::is_token($string, $i)) {
+ if ($string{$i} === '/' && @$string{$i + 1} === '*') {
+ $this->status = 'ic';
+ ++$i;
+ $this->from[] = 'at';
+ } elseif ($string{$i} === '{') {
+ $this->status = 'is';
+ $this->at = $this->css_new_media_section($this->at);
+ $this->_add_token(AT_START, $this->at);
+ } elseif ($string{$i} === ',') {
+ $this->at = trim($this->at) . ',';
+ } elseif ($string{$i} === '\\') {
+ $this->at .= $this->_unicode($string, $i);
+ }
+ // fix for complicated media, i.e @media screen and (-webkit-min-device-pixel-ratio:1.5)
+ // '/' is included for ratios in Opera: (-o-min-device-pixel-ratio: 3/2)
+ elseif (in_array($string{$i}, array('(', ')', ':', '.', '/'))) {
+ $this->at .= $string{$i};
+ }
+ } else {
+ $lastpos = strlen($this->at) - 1;
+ if (!( (ctype_space($this->at{$lastpos}) || csstidy::is_token($this->at, $lastpos) && $this->at{$lastpos} === ',') && ctype_space($string{$i}))) {
+ $this->at .= $string{$i};
+ }
+ }
+ break;
+
+ /* Case in-selector */
+ case 'is':
+ if (csstidy::is_token($string, $i)) {
+ if ($string{$i} === '/' && @$string{$i + 1} === '*' && trim($this->selector) == '') {
+ $this->status = 'ic';
+ ++$i;
+ $this->from[] = 'is';
+ } elseif ($string{$i} === '@' && trim($this->selector) == '') {
+ // Check for at-rule
+ $this->invalid_at = true;
+ foreach ($at_rules as $name => $type) {
+ if (!strcasecmp(substr($string, $i + 1, strlen($name)), $name)) {
+ ($type === 'at') ? $this->at = '@' . $name : $this->selector = '@' . $name;
+ $this->status = $type;
+ $i += strlen($name);
+ $this->invalid_at = false;
+ }
+ }
+
+ if ($this->invalid_at) {
+ $this->selector = '@';
+ $invalid_at_name = '';
+ for ($j = $i + 1; $j < $size; ++$j) {
+ if (!ctype_alpha($string{$j})) {
+ break;
+ }
+ $invalid_at_name .= $string{$j};
+ }
+ $this->log('Invalid @-rule: ' . $invalid_at_name . ' (removed)', 'Warning');
+ }
+ } elseif (($string{$i} === '"' || $string{$i} === "'")) {
+ $this->cur_string[] = $string{$i};
+ $this->status = 'instr';
+ $this->str_char[] = $string{$i};
+ $this->from[] = 'is';
+ /* fixing CSS3 attribute selectors, i.e. a[href$=".mp3" */
+ $this->quoted_string[] = ($string{$i - 1} == '=' );
+ } elseif ($this->invalid_at && $string{$i} === ';') {
+ $this->invalid_at = false;
+ $this->status = 'is';
+ } elseif ($string{$i} === '{') {
+ $this->status = 'ip';
+ if($this->at == '') {
+ $this->at = $this->css_new_media_section(DEFAULT_AT);
+ }
+ $this->selector = $this->css_new_selector($this->at,$this->selector);
+ $this->_add_token(SEL_START, $this->selector);
+ $this->added = false;
+ } elseif ($string{$i} === '}') {
+ $this->_add_token(AT_END, $this->at);
+ $this->at = '';
+ $this->selector = '';
+ $this->sel_separate = array();
+ } elseif ($string{$i} === ',') {
+ $this->selector = trim($this->selector) . ',';
+ $this->sel_separate[] = strlen($this->selector);
+ } elseif ($string{$i} === '\\') {
+ $this->selector .= $this->_unicode($string, $i);
+ } elseif ($string{$i} === '*' && @in_array($string{$i + 1}, array('.', '#', '[', ':'))) {
+ // remove unnecessary universal selector, FS#147
+ } else {
+ $this->selector .= $string{$i};
+ }
+ } else {
+ $lastpos = strlen($this->selector) - 1;
+ if ($lastpos == -1 || !( (ctype_space($this->selector{$lastpos}) || csstidy::is_token($this->selector, $lastpos) && $this->selector{$lastpos} === ',') && ctype_space($string{$i}))) {
+ $this->selector .= $string{$i};
+ }
+ else if (ctype_space($string{$i}) && $this->get_cfg('preserve_css') && !$this->get_cfg('merge_selectors')) {
+ $this->selector .= $string{$i};
+ }
+ }
+ break;
+
+ /* Case in-property */
+ case 'ip':
+ if (csstidy::is_token($string, $i)) {
+ if (($string{$i} === ':' || $string{$i} === '=') && $this->property != '') {
+ $this->status = 'iv';
+ if (!$this->get_cfg('discard_invalid_properties') || csstidy::property_is_valid($this->property)) {
+ $this->property = $this->css_new_property($this->at,$this->selector,$this->property);
+ $this->_add_token(PROPERTY, $this->property);
+ }
+ } elseif ($string{$i} === '/' && @$string{$i + 1} === '*' && $this->property == '') {
+ $this->status = 'ic';
+ ++$i;
+ $this->from[] = 'ip';
+ } elseif ($string{$i} === '}') {
+ $this->explode_selectors();
+ $this->status = 'is';
+ $this->invalid_at = false;
+ $this->_add_token(SEL_END, $this->selector);
+ $this->selector = '';
+ $this->property = '';
+ } elseif ($string{$i} === ';') {
+ $this->property = '';
+ } elseif ($string{$i} === '\\') {
+ $this->property .= $this->_unicode($string, $i);
+ }
+ // else this is dumb IE a hack, keep it
+ elseif ($this->property=='' AND !ctype_space($string{$i})) {
+ $this->property .= $string{$i};
+ }
+ }
+ elseif (!ctype_space($string{$i})) {
+ $this->property .= $string{$i};
+ }
+ break;
+
+ /* Case in-value */
+ case 'iv':
+ $pn = (($string{$i} === "\n" || $string{$i} === "\r") && $this->property_is_next($string, $i + 1) || $i == strlen($string) - 1);
+ if ((csstidy::is_token($string, $i) || $pn) && (!($string{$i} == ',' && !ctype_space($string{$i+1})))) {
+ if ($string{$i} === '/' && @$string{$i + 1} === '*') {
+ $this->status = 'ic';
+ ++$i;
+ $this->from[] = 'iv';
+ } elseif (($string{$i} === '"' || $string{$i} === "'" || $string{$i} === '(')) {
+ $this->cur_string[] = $string{$i};
+ $this->str_char[] = ($string{$i} === '(') ? ')' : $string{$i};
+ $this->status = 'instr';
+ $this->from[] = 'iv';
+ $this->quoted_string[] = in_array(strtolower($this->property), $quoted_string_properties);
+ } elseif ($string{$i} === ',') {
+ $this->sub_value = trim($this->sub_value) . ',';
+ } elseif ($string{$i} === '\\') {
+ $this->sub_value .= $this->_unicode($string, $i);
+ } elseif ($string{$i} === ';' || $pn) {
+ if ($this->selector{0} === '@' && isset($at_rules[substr($this->selector, 1)]) && $at_rules[substr($this->selector, 1)] === 'iv') {
+ $this->status = 'is';
+
+ switch ($this->selector) {
+ case '@charset':
+ /* Add quotes to charset */
+ $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
+ $this->charset = $this->sub_value_arr[0];
+ break;
+ case '@namespace':
+ /* Add quotes to namespace */
+ $this->sub_value_arr[] = '"' . trim($this->sub_value) . '"';
+ $this->namespace = implode(' ', $this->sub_value_arr);
+ break;
+ case '@import':
+ $this->sub_value = trim($this->sub_value);
+
+ if (empty($this->sub_value_arr)) {
+ // Quote URLs in imports only if they're not already inside url() and not already quoted.
+ if (substr($this->sub_value, 0, 4) != 'url(') {
+ if (!($this->sub_value{0} == substr($this->sub_value, -1) && in_array($this->sub_value{0}, array("'", '"')))) {
+ $this->sub_value = '"' . $this->sub_value . '"';
+ }
+ }
+ }
+
+ $this->sub_value_arr[] = $this->sub_value;
+ $this->import[] = implode(' ', $this->sub_value_arr);
+ break;
+ }
+
+ $this->sub_value_arr = array();
+ $this->sub_value = '';
+ $this->selector = '';
+ $this->sel_separate = array();
+ } else {
+ $this->status = 'ip';
+ }
+ } elseif ($string{$i} !== '}') {
+ $this->sub_value .= $string{$i};
+ }
+ if (($string{$i} === '}' || $string{$i} === ';' || $pn) && !empty($this->selector)) {
+ if ($this->at == '') {
+ $this->at = $this->css_new_media_section(DEFAULT_AT);
+ }
+
+ // case settings
+ if ($this->get_cfg('lowercase_s')) {
+ $this->selector = strtolower($this->selector);
+ }
+ $this->property = strtolower($this->property);
+
+ $this->optimise->subvalue();
+ if ($this->sub_value != '') {
+ if (substr($this->sub_value, 0, 6) == 'format') {
+ $format_strings = csstidy::parse_string_list(substr($this->sub_value, 7, -1));
+ if (!$format_strings) {
+ $this->sub_value = "";
+ }
+ else {
+ $this->sub_value = "format(";
+
+ foreach ($format_strings as $format_string) {
+ $this->sub_value .= '"' . str_replace('"', '\\"', $format_string) . '",';
+ }
+
+ $this->sub_value = substr($this->sub_value, 0, -1) . ")";
+ }
+ }
+ if ($this->sub_value != '') {
+ $this->sub_value_arr[] = $this->sub_value;
+ }
+ $this->sub_value = '';
+ }
+
+ $this->value = array_shift($this->sub_value_arr);
+ while(count($this->sub_value_arr)){
+ //$this->value .= (substr($this->value,-1,1)==','?'':' ').array_shift($this->sub_value_arr);
+ $this->value .= ' '.array_shift($this->sub_value_arr);
+ }
+
+ $this->optimise->value();
+
+ $valid = csstidy::property_is_valid($this->property);
+ if ((!$this->invalid_at || $this->get_cfg('preserve_css')) && (!$this->get_cfg('discard_invalid_properties') || $valid)) {
+ $this->css_add_property($this->at, $this->selector, $this->property, $this->value);
+ $this->_add_token(VALUE, $this->value);
+ $this->optimise->shorthands();
+ }
+ if (!$valid) {
+ if ($this->get_cfg('discard_invalid_properties')) {
+ $this->log('Removed invalid property: ' . $this->property, 'Warning');
+ } else {
+ $this->log('Invalid property in ' . strtoupper($this->get_cfg('css_level')) . ': ' . $this->property, 'Warning');
+ }
+ }
+
+ $this->property = '';
+ $this->sub_value_arr = array();
+ $this->value = '';
+ }
+ if ($string{$i} === '}') {
+ $this->explode_selectors();
+ $this->_add_token(SEL_END, $this->selector);
+ $this->status = 'is';
+ $this->invalid_at = false;
+ $this->selector = '';
+ }
+ } elseif (!$pn) {
+ $this->sub_value .= $string{$i};
+
+ if (ctype_space($string{$i}) || $string{$i} == ',') {
+ $this->optimise->subvalue();
+ if ($this->sub_value != '') {
+ $this->sub_value_arr[] = $this->sub_value;
+ $this->sub_value = '';
+ }
+ }
+ }
+ break;
+
+ /* Case in string */
+ case 'instr':
+ $_str_char = $this->str_char[count($this->str_char)-1];
+ $_cur_string = $this->cur_string[count($this->cur_string)-1];
+ $temp_add = $string{$i};
+
+ // Add another string to the stack. Strings can't be nested inside of quotes, only parentheses, but
+ // parentheticals can be nested more than once.
+ if ($_str_char === ")" && ($string{$i} === "(" || $string{$i} === '"' || $string{$i} === '\'') && !csstidy::escaped($string, $i)) {
+ $this->cur_string[] = $string{$i};
+ $this->str_char[] = $string{$i} == "(" ? ")" : $string{$i};
+ $this->from[] = 'instr';
+ $this->quoted_string[] = !($string{$i} === "(");
+ continue 2;
+ }
+
+ if ($_str_char !== ")" && ($string{$i} === "\n" || $string{$i} === "\r") && !($string{$i - 1} === '\\' && !csstidy::escaped($string, $i - 1))) {
+ $temp_add = "\\A";
+ $this->log('Fixed incorrect newline in string', 'Warning');
+ }
+
+ $_cur_string .= $temp_add;
+
+ if ($string{$i} === $_str_char && !csstidy::escaped($string, $i)) {
+ $_quoted_string = array_pop($this->quoted_string);
+
+ $this->status = array_pop($this->from);
+
+ if (!preg_match('|[' . implode('', $GLOBALS['csstidy']['whitespace']) . ']|uis', $_cur_string) && $this->property !== 'content') {
+ if (!$_quoted_string) {
+ if ($_str_char !== ')') {
+ // Convert properties like
+ // font-family: 'Arial';
+ // to
+ // font-family: Arial;
+ // or
+ // url("abc")
+ // to
+ // url(abc)
+ $_cur_string = substr($_cur_string, 1, -1);
+ }
+ } else {
+ $_quoted_string = false;
+ }
+ }
+
+ array_pop($this->cur_string);
+ array_pop($this->str_char);
+
+ if ($_str_char === ")") {
+ $_cur_string = "(" . trim(substr($_cur_string, 1, -1)) . ")";
+ }
+
+ if ($this->status === 'iv') {
+ if (!$_quoted_string){
+ if (strpos($_cur_string,',')!==false)
+ // we can on only remove space next to ','
+ $_cur_string = implode(',',array_map('trim',explode(',',$_cur_string)));
+ // and multiple spaces (too expensive)
+ if (strpos($_cur_string,' ')!==false)
+ $_cur_string = preg_replace(",\s+,"," ",$_cur_string);
+ }
+ $this->sub_value .= $_cur_string;
+ } elseif ($this->status === 'is') {
+ $this->selector .= $_cur_string;
+ } elseif ($this->status === 'instr') {
+ $this->cur_string[count($this->cur_string)-1] .= $_cur_string;
+ }
+ }
+ else {
+ $this->cur_string[count($this->cur_string)-1] = $_cur_string;
+ }
+ break;
+
+ /* Case in-comment */
+ case 'ic':
+ if ($string{$i} === '*' && $string{$i + 1} === '/') {
+ $this->status = array_pop($this->from);
+ $i++;
+ $this->_add_token(COMMENT, $cur_comment);
+ $cur_comment = '';
+ } else {
+ $cur_comment .= $string{$i};
+ }
+ break;
+ }
+ }
+
+ $this->optimise->postparse();
+
+ $this->print->_reset();
+
+ @setlocale(LC_ALL, $old); // Set locale back to original setting
+
+ return!(empty($this->css) && empty($this->import) && empty($this->charset) && empty($this->tokens) && empty($this->namespace));
+ }
+
+ /**
+ * Explodes selectors
+ * @access private
+ * @version 1.0
+ */
+ function explode_selectors() {
+ // Explode multiple selectors
+ if ($this->get_cfg('merge_selectors') === 1) {
+ $new_sels = array();
+ $lastpos = 0;
+ $this->sel_separate[] = strlen($this->selector);
+ foreach ($this->sel_separate as $num => $pos) {
+ if ($num == count($this->sel_separate) - 1) {
+ $pos += 1;
+ }
+
+ $new_sels[] = substr($this->selector, $lastpos, $pos - $lastpos - 1);
+ $lastpos = $pos;
+ }
+
+ if (count($new_sels) > 1) {
+ foreach ($new_sels as $selector) {
+ if (isset($this->css[$this->at][$this->selector])) {
+ $this->merge_css_blocks($this->at, $selector, $this->css[$this->at][$this->selector]);
+ }
+ }
+ unset($this->css[$this->at][$this->selector]);
+ }
+ }
+ $this->sel_separate = array();
+ }
+
+ /**
+ * Checks if a character is escaped (and returns true if it is)
+ * @param string $string
+ * @param integer $pos
+ * @access public
+ * @return bool
+ * @version 1.02
+ */
+ static function escaped(&$string, $pos) {
+ return!(@($string{$pos - 1} !== '\\') || csstidy::escaped($string, $pos - 1));
+ }
+
+ /**
+ * Adds a property with value to the existing CSS code
+ * @param string $media
+ * @param string $selector
+ * @param string $property
+ * @param string $new_val
+ * @access private
+ * @version 1.2
+ */
+ function css_add_property($media, $selector, $property, $new_val) {
+ if ($this->get_cfg('preserve_css') || trim($new_val) == '') {
+ return;
+ }
+
+ $this->added = true;
+ if (isset($this->css[$media][$selector][$property])) {
+ if ((csstidy::is_important($this->css[$media][$selector][$property]) && csstidy::is_important($new_val)) || !csstidy::is_important($this->css[$media][$selector][$property])) {
+ $this->css[$media][$selector][$property] = trim($new_val);
+ }
+ } else {
+ $this->css[$media][$selector][$property] = trim($new_val);
+ }
+ }
+
+ /**
+ * Start a new media section.
+ * Check if the media is not already known,
+ * else rename it with extra spaces
+ * to avoid merging
+ *
+ * @param string $media
+ * @return string
+ */
+ function css_new_media_section($media){
+ if($this->get_cfg('preserve_css')) {
+ return $media;
+ }
+
+ // if the last @media is the same as this
+ // keep it
+ if (!$this->css OR !is_array($this->css) OR empty($this->css)){
+ return $media;
+ }
+ end($this->css);
+ $at = current( $this->css );
+ if ($at == $media){
+ return $media;
+ }
+ while (isset($this->css[$media]))
+ if (is_numeric($media))
+ $media++;
+ else
+ $media .= " ";
+ return $media;
+ }
+
+ /**
+ * Start a new selector.
+ * If already referenced in this media section,
+ * rename it with extra space to avoid merging
+ * except if merging is required,
+ * or last selector is the same (merge siblings)
+ *
+ * never merge @font-face
+ *
+ * @param string $media
+ * @param string $selector
+ * @return string
+ */
+ function css_new_selector($media,$selector){
+ if($this->get_cfg('preserve_css')) {
+ return $selector;
+ }
+ $selector = trim($selector);
+ if (strncmp($selector,"@font-face",10)!=0){
+ if ($this->settings['merge_selectors'] != false)
+ return $selector;
+
+ if (!$this->css OR !isset($this->css[$media]) OR !$this->css[$media])
+ return $selector;
+
+ // if last is the same, keep it
+ end($this->css[$media]);
+ $sel = current( $this->css[$media] );
+ if ($sel == $selector){
+ return $selector;
+ }
+ }
+
+ while (isset($this->css[$media][$selector]))
+ $selector .= " ";
+ return $selector;
+ }
+
+ /**
+ * Start a new propertie.
+ * If already references in this selector,
+ * rename it with extra space to avoid override
+ *
+ * @param string $media
+ * @param string $selector
+ * @param string $property
+ * @return string
+ */
+ function css_new_property($media, $selector, $property){
+ if($this->get_cfg('preserve_css')) {
+ return $property;
+ }
+ if (!$this->css OR !isset($this->css[$media][$selector]) OR !$this->css[$media][$selector])
+ return $property;
+
+ while (isset($this->css[$media][$selector][$property]))
+ $property .= " ";
+
+ return $property;
+ }
+
+ /**
+ * Adds CSS to an existing media/selector
+ * @param string $media
+ * @param string $selector
+ * @param array $css_add
+ * @access private
+ * @version 1.1
+ */
+ function merge_css_blocks($media, $selector, $css_add) {
+ foreach ($css_add as $property => $value) {
+ $this->css_add_property($media, $selector, $property, $value, false);
+ }
+ }
+
+ /**
+ * Checks if $value is !important.
+ * @param string $value
+ * @return bool
+ * @access public
+ * @version 1.0
+ */
+ static function is_important(&$value) {
+ return (!strcasecmp(substr(str_replace($GLOBALS['csstidy']['whitespace'], '', $value), -10, 10), '!important'));
+ }
+
+ /**
+ * Returns a value without !important
+ * @param string $value
+ * @return string
+ * @access public
+ * @version 1.0
+ */
+ static function gvw_important($value) {
+ if (csstidy::is_important($value)) {
+ $value = trim($value);
+ $value = substr($value, 0, -9);
+ $value = trim($value);
+ $value = substr($value, 0, -1);
+ $value = trim($value);
+ return $value;
+ }
+ return $value;
+ }
+
+ /**
+ * Checks if the next word in a string from pos is a CSS property
+ * @param string $istring
+ * @param integer $pos
+ * @return bool
+ * @access private
+ * @version 1.2
+ */
+ function property_is_next($istring, $pos) {
+ $all_properties = & $GLOBALS['csstidy']['all_properties'];
+ $istring = substr($istring, $pos, strlen($istring) - $pos);
+ $pos = strpos($istring, ':');
+ if ($pos === false) {
+ return false;
+ }
+ $istring = strtolower(trim(substr($istring, 0, $pos)));
+ if (isset($all_properties[$istring])) {
+ $this->log('Added semicolon to the end of declaration', 'Warning');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a property is valid
+ * @param string $property
+ * @return bool;
+ * @access public
+ * @version 1.0
+ */
+ function property_is_valid($property) {
+ $property = strtolower($property);
+ if (in_array(trim($property), $GLOBALS['csstidy']['multiple_properties'])) $property = trim($property);
+ $all_properties = & $GLOBALS['csstidy']['all_properties'];
+ return (isset($all_properties[$property]) && strpos($all_properties[$property], strtoupper($this->get_cfg('css_level'))) !== false );
+ }
+
+ /**
+ * Accepts a list of strings (e.g., the argument to format() in a @font-face src property)
+ * and returns a list of the strings. Converts things like:
+ *
+ * format(abc) => format("abc")
+ * format(abc def) => format("abc","def")
+ * format(abc "def") => format("abc","def")
+ * format(abc, def, ghi) => format("abc","def","ghi")
+ * format("abc",'def') => format("abc","def")
+ * format("abc, def, ghi") => format("abc, def, ghi")
+ *
+ * @param string
+ * @return array
+ */
+
+ function parse_string_list($value) {
+ $value = trim($value);
+
+ // Case: empty
+ if (!$value) return array();
+
+ $strings = array();
+
+ $in_str = false;
+ $current_string = "";
+
+ for ($i = 0, $_len = strlen($value); $i < $_len; $i++) {
+ if (($value{$i} == "," || $value{$i} === " ") && $in_str === true) {
+ $in_str = false;
+ $strings[] = $current_string;
+ $current_string = "";
+ }
+ else if ($value{$i} == '"' || $value{$i} == "'"){
+ if ($in_str === $value{$i}) {
+ $strings[] = $current_string;
+ $in_str = false;
+ $current_string = "";
+ continue;
+ }
+ else if (!$in_str) {
+ $in_str = $value{$i};
+ }
+ }
+ else {
+ if ($in_str){
+ $current_string .= $value{$i};
+ }
+ else {
+ if (!preg_match("/[\s,]/", $value{$i})) {
+ $in_str = true;
+ $current_string = $value{$i};
+ }
+ }
+ }
+ }
+
+ if ($current_string) {
+ $strings[] = $current_string;
+ }
+
+ return $strings;
+ }
+}
diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php
new file mode 100644
index 00000000..bc5accc5
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_ctype.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * CSSTidy - CSS Parser and Optimiser
+ *
+ * CSS ctype functions
+ * Defines some functions that can be not defined.
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CSSTidy; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
+ * @package csstidy
+ * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
+ * @version 1.0
+ */
+/* ctype_space Check for whitespace character(s) */
+if (!function_exists('ctype_space')) {
+
+ function ctype_space($text) {
+ return!preg_match("/[^\s\r\n\t\f]/", $text);
+ }
+
+}
+/* ctype_alpha Check for alphabetic character(s) */
+if (!function_exists('ctype_alpha')) {
+
+ function ctype_alpha($text) {
+ return preg_match("/[a-zA-Z]/", $text);
+ }
+
+}
+?> \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php
new file mode 100644
index 00000000..176e0fd3
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_optimise.php
@@ -0,0 +1,938 @@
+<?php
+
+/**
+ * CSSTidy - CSS Parser and Optimiser
+ *
+ * CSS Optimising Class
+ * This class optimises CSS data generated by csstidy.
+ *
+ * Copyright 2005, 2006, 2007 Florian Schmitz
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2007
+ * @author Brett Zamir (brettz9 at yahoo dot com) 2007
+ * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
+ */
+
+/**
+ * CSS Optimising Class
+ *
+ * This class optimises CSS data generated by csstidy.
+ *
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2006
+ * @version 1.0
+ */
+class csstidy_optimise {
+ /**
+ * Constructor
+ * @param array $css contains the class csstidy
+ * @access private
+ * @version 1.0
+ */
+ function __construct(&$css) {
+ $this->parser = & $css;
+ $this->css = & $css->css;
+ $this->sub_value = & $css->sub_value;
+ $this->at = & $css->at;
+ $this->selector = & $css->selector;
+ $this->property = & $css->property;
+ $this->value = & $css->value;
+ }
+
+ function csstidy_optimise(&$css) {
+ $this->__construct($css);
+ }
+
+ /**
+ * Optimises $css after parsing
+ * @access public
+ * @version 1.0
+ */
+ function postparse() {
+ if ($this->parser->get_cfg('preserve_css')) {
+ return;
+ }
+
+ if ($this->parser->get_cfg('merge_selectors') === 2) {
+ foreach ($this->css as $medium => $value) {
+ $this->merge_selectors($this->css[$medium]);
+ }
+ }
+
+ if ($this->parser->get_cfg('discard_invalid_selectors')) {
+ foreach ($this->css as $medium => $value) {
+ $this->discard_invalid_selectors($this->css[$medium]);
+ }
+ }
+
+ if ($this->parser->get_cfg('optimise_shorthands') > 0) {
+ foreach ($this->css as $medium => $value) {
+ foreach ($value as $selector => $value1) {
+ $this->css[$medium][$selector] = csstidy_optimise::merge_4value_shorthands($this->css[$medium][$selector]);
+
+ if ($this->parser->get_cfg('optimise_shorthands') < 2) {
+ continue;
+ }
+
+ $this->css[$medium][$selector] = csstidy_optimise::merge_font($this->css[$medium][$selector]);
+
+ if ($this->parser->get_cfg('optimise_shorthands') < 3) {
+ continue;
+ }
+
+ $this->css[$medium][$selector] = csstidy_optimise::merge_bg($this->css[$medium][$selector]);
+ if (empty($this->css[$medium][$selector])) {
+ unset($this->css[$medium][$selector]);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Optimises values
+ * @access public
+ * @version 1.0
+ */
+ function value() {
+ $shorthands = & $GLOBALS['csstidy']['shorthands'];
+
+ // optimise shorthand properties
+ if (isset($shorthands[$this->property])) {
+ $temp = csstidy_optimise::shorthand($this->value); // FIXME - move
+ if ($temp != $this->value) {
+ $this->parser->log('Optimised shorthand notation (' . $this->property . '): Changed "' . $this->value . '" to "' . $temp . '"', 'Information');
+ }
+ $this->value = $temp;
+ }
+
+ // Remove whitespace at ! important
+ if ($this->value != $this->compress_important($this->value)) {
+ $this->parser->log('Optimised !important', 'Information');
+ }
+ }
+
+ /**
+ * Optimises shorthands
+ * @access public
+ * @version 1.0
+ */
+ function shorthands() {
+ $shorthands = & $GLOBALS['csstidy']['shorthands'];
+
+ if (!$this->parser->get_cfg('optimise_shorthands') || $this->parser->get_cfg('preserve_css')) {
+ return;
+ }
+
+ if ($this->property === 'font' && $this->parser->get_cfg('optimise_shorthands') > 1) {
+ $this->css[$this->at][$this->selector]['font']='';
+ $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_font($this->value));
+ }
+ if ($this->property === 'background' && $this->parser->get_cfg('optimise_shorthands') > 2) {
+ $this->css[$this->at][$this->selector]['background']='';
+ $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_short_bg($this->value));
+ }
+ if (isset($shorthands[$this->property])) {
+ $this->parser->merge_css_blocks($this->at, $this->selector, csstidy_optimise::dissolve_4value_shorthands($this->property, $this->value));
+ if (is_array($shorthands[$this->property])) {
+ $this->css[$this->at][$this->selector][$this->property] = '';
+ }
+ }
+ }
+
+ /**
+ * Optimises a sub-value
+ * @access public
+ * @version 1.0
+ */
+ function subvalue() {
+ $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
+
+ $this->sub_value = trim($this->sub_value);
+ if ($this->sub_value == '') { // caution : '0'
+ return;
+ }
+
+ $important = '';
+ if (csstidy::is_important($this->sub_value)) {
+ $important = '!important';
+ }
+ $this->sub_value = csstidy::gvw_important($this->sub_value);
+
+ // Compress font-weight
+ if ($this->property === 'font-weight' && $this->parser->get_cfg('compress_font-weight')) {
+ if ($this->sub_value === 'bold') {
+ $this->sub_value = '700';
+ $this->parser->log('Optimised font-weight: Changed "bold" to "700"', 'Information');
+ } else if ($this->sub_value === 'normal') {
+ $this->sub_value = '400';
+ $this->parser->log('Optimised font-weight: Changed "normal" to "400"', 'Information');
+ }
+ }
+
+ $temp = $this->compress_numbers($this->sub_value);
+ if (strcasecmp($temp, $this->sub_value) !== 0) {
+ if (strlen($temp) > strlen($this->sub_value)) {
+ $this->parser->log('Fixed invalid number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
+ } else {
+ $this->parser->log('Optimised number: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
+ }
+ $this->sub_value = $temp;
+ }
+ if ($this->parser->get_cfg('compress_colors')) {
+ $temp = $this->cut_color($this->sub_value);
+ if ($temp !== $this->sub_value) {
+ if (isset($replace_colors[$this->sub_value])) {
+ $this->parser->log('Fixed invalid color name: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Warning');
+ } else {
+ $this->parser->log('Optimised color: Changed "' . $this->sub_value . '" to "' . $temp . '"', 'Information');
+ }
+ $this->sub_value = $temp;
+ }
+ }
+ $this->sub_value .= $important;
+ }
+
+ /**
+ * Compresses shorthand values. Example: margin:1px 1px 1px 1px -> margin:1px
+ * @param string $value
+ * @access public
+ * @return string
+ * @version 1.0
+ */
+ static function shorthand($value) {
+ $important = '';
+ if (csstidy::is_important($value)) {
+ $values = csstidy::gvw_important($value);
+ $important = '!important';
+ }
+ else
+ $values = $value;
+
+ $values = explode(' ', $values);
+ switch (count($values)) {
+ case 4:
+ if ($values[0] == $values[1] && $values[0] == $values[2] && $values[0] == $values[3]) {
+ return $values[0] . $important;
+ } elseif ($values[1] == $values[3] && $values[0] == $values[2]) {
+ return $values[0] . ' ' . $values[1] . $important;
+ } elseif ($values[1] == $values[3]) {
+ return $values[0] . ' ' . $values[1] . ' ' . $values[2] . $important;
+ }
+ break;
+
+ case 3:
+ if ($values[0] == $values[1] && $values[0] == $values[2]) {
+ return $values[0] . $important;
+ } elseif ($values[0] == $values[2]) {
+ return $values[0] . ' ' . $values[1] . $important;
+ }
+ break;
+
+ case 2:
+ if ($values[0] == $values[1]) {
+ return $values[0] . $important;
+ }
+ break;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Removes unnecessary whitespace in ! important
+ * @param string $string
+ * @return string
+ * @access public
+ * @version 1.1
+ */
+ function compress_important(&$string) {
+ if (csstidy::is_important($string)) {
+ $string = csstidy::gvw_important($string) . ' !important'; }
+ return $string;
+ }
+
+ /**
+ * Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
+ * @param string $color
+ * @return string
+ * @version 1.1
+ */
+ function cut_color($color) {
+ $replace_colors = & $GLOBALS['csstidy']['replace_colors'];
+
+ // rgb(0,0,0) -> #000000 (or #000 in this case later)
+ if (strtolower(substr($color, 0, 4)) === 'rgb(') {
+ $color_tmp = substr($color, 4, strlen($color) - 5);
+ $color_tmp = explode(',', $color_tmp);
+ for ($i = 0; $i < count($color_tmp); $i++) {
+ $color_tmp[$i] = trim($color_tmp[$i]);
+ if (substr($color_tmp[$i], -1) === '%') {
+ $color_tmp[$i] = round((255 * $color_tmp[$i]) / 100);
+ }
+ if ($color_tmp[$i] > 255)
+ $color_tmp[$i] = 255;
+ }
+ $color = '#';
+ for ($i = 0; $i < 3; $i++) {
+ if ($color_tmp[$i] < 16) {
+ $color .= '0' . dechex($color_tmp[$i]);
+ } else {
+ $color .= dechex($color_tmp[$i]);
+ }
+ }
+ }
+
+ // Fix bad color names
+ if (isset($replace_colors[strtolower($color)])) {
+ $color = $replace_colors[strtolower($color)];
+ }
+
+ // #aabbcc -> #abc
+ if (strlen($color) == 7) {
+ $color_temp = strtolower($color);
+ if ($color_temp{0} === '#' && $color_temp{1} == $color_temp{2} && $color_temp{3} == $color_temp{4} && $color_temp{5} == $color_temp{6}) {
+ $color = '#' . $color{1} . $color{3} . $color{5};
+ }
+ }
+
+ switch (strtolower($color)) {
+ /* color name -> hex code */
+ case 'black': return '#000';
+ case 'fuchsia': return '#f0f';
+ case 'white': return '#fff';
+ case 'yellow': return '#ff0';
+
+ /* hex code -> color name */
+ case '#800000': return 'maroon';
+ case '#ffa500': return 'orange';
+ case '#808000': return 'olive';
+ case '#800080': return 'purple';
+ case '#008000': return 'green';
+ case '#000080': return 'navy';
+ case '#008080': return 'teal';
+ case '#c0c0c0': return 'silver';
+ case '#808080': return 'gray';
+ case '#f00': return 'red';
+ }
+
+ return $color;
+ }
+
+ /**
+ * Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
+ * @param string $subvalue
+ * @return string
+ * @version 1.2
+ */
+ function compress_numbers($subvalue) {
+ $unit_values = & $GLOBALS['csstidy']['unit_values'];
+ $color_values = & $GLOBALS['csstidy']['color_values'];
+
+ // for font:1em/1em sans-serif...;
+ if ($this->property === 'font') {
+ $temp = explode('/', $subvalue);
+ } else {
+ $temp = array($subvalue);
+ }
+ for ($l = 0; $l < count($temp); $l++) {
+ // if we are not dealing with a number at this point, do not optimise anything
+ $number = $this->AnalyseCssNumber($temp[$l]);
+ if ($number === false) {
+ return $subvalue;
+ }
+
+ // Fix bad colors
+ if (in_array($this->property, $color_values)) {
+ if (strlen($temp[$l]) == 3 || strlen($temp[$l]) == 6) {
+ $temp[$l] = '#' . $temp[$l];
+ }
+ else {
+ $temp[$l] = "0";
+ }
+ continue;
+ }
+
+ if (abs($number[0]) > 0) {
+ if ($number[1] == '' && in_array($this->property, $unit_values, true)) {
+ $number[1] = 'px';
+ }
+ } else {
+ $number[1] = '';
+ }
+
+ $temp[$l] = $number[0] . $number[1];
+ }
+
+ return ((count($temp) > 1) ? $temp[0] . '/' . $temp[1] : $temp[0]);
+ }
+
+ /**
+ * Checks if a given string is a CSS valid number. If it is,
+ * an array containing the value and unit is returned
+ * @param string $string
+ * @return array ('unit' if unit is found or '' if no unit exists, number value) or false if no number
+ */
+ function AnalyseCssNumber($string) {
+ // most simple checks first
+ if (strlen($string) == 0 || ctype_alpha($string{0})) {
+ return false;
+ }
+
+ $units = & $GLOBALS['csstidy']['units'];
+ $return = array(0, '');
+
+ $return[0] = floatval($string);
+ if (abs($return[0]) > 0 && abs($return[0]) < 1) {
+ if ($return[0] < 0) {
+ $return[0] = '-' . ltrim(substr($return[0], 1), '0');
+ } else {
+ $return[0] = ltrim($return[0], '0');
+ }
+ }
+
+ // Look for unit and split from value if exists
+ foreach ($units as $unit) {
+ $expectUnitAt = strlen($string) - strlen($unit);
+ if (!($unitInString = stristr($string, $unit))) { // mb_strpos() fails with "false"
+ continue;
+ }
+ $actualPosition = strpos($string, $unitInString);
+ if ($expectUnitAt === $actualPosition) {
+ $return[1] = $unit;
+ $string = substr($string, 0, - strlen($unit));
+ break;
+ }
+ }
+ if (!is_numeric($string)) {
+ return false;
+ }
+ return $return;
+ }
+
+ /**
+ * Merges selectors with same properties. Example: a{color:red} b{color:red} -> a,b{color:red}
+ * Very basic and has at least one bug. Hopefully there is a replacement soon.
+ * @param array $array
+ * @return array
+ * @access public
+ * @version 1.2
+ */
+ function merge_selectors(&$array) {
+ $css = $array;
+ foreach ($css as $key => $value) {
+ if (!isset($css[$key])) {
+ continue;
+ }
+ $newsel = '';
+
+ // Check if properties also exist in another selector
+ $keys = array();
+ // PHP bug (?) without $css = $array; here
+ foreach ($css as $selector => $vali) {
+ if ($selector == $key) {
+ continue;
+ }
+
+ if ($css[$key] === $vali) {
+ $keys[] = $selector;
+ }
+ }
+
+ if (!empty($keys)) {
+ $newsel = $key;
+ unset($css[$key]);
+ foreach ($keys as $selector) {
+ unset($css[$selector]);
+ $newsel .= ',' . $selector;
+ }
+ $css[$newsel] = $value;
+ }
+ }
+ $array = $css;
+ }
+
+ /**
+ * Removes invalid selectors and their corresponding rule-sets as
+ * defined by 4.1.7 in REC-CSS2. This is a very rudimentary check
+ * and should be replaced by a full-blown parsing algorithm or
+ * regular expression
+ * @version 1.4
+ */
+ function discard_invalid_selectors(&$array) {
+ $invalid = array('+' => true, '~' => true, ',' => true, '>' => true);
+ foreach ($array as $selector => $decls) {
+ $ok = true;
+ $selectors = array_map('trim', explode(',', $selector));
+ foreach ($selectors as $s) {
+ $simple_selectors = preg_split('/\s*[+>~\s]\s*/', $s);
+ foreach ($simple_selectors as $ss) {
+ if ($ss === '')
+ $ok = false;
+ // could also check $ss for internal structure,
+ // but that probably would be too slow
+ }
+ }
+ if (!$ok)
+ unset($array[$selector]);
+ }
+ }
+
+ /**
+ * Dissolves properties like padding:10px 10px 10px to padding-top:10px;padding-bottom:10px;...
+ * @param string $property
+ * @param string $value
+ * @return array
+ * @version 1.0
+ * @see merge_4value_shorthands()
+ */
+ static function dissolve_4value_shorthands($property, $value) {
+ $shorthands = & $GLOBALS['csstidy']['shorthands'];
+ if (!is_array($shorthands[$property])) {
+ $return[$property] = $value;
+ return $return;
+ }
+
+ $important = '';
+ if (csstidy::is_important($value)) {
+ $value = csstidy::gvw_important($value);
+ $important = '!important';
+ }
+ $values = explode(' ', $value);
+
+
+ $return = array();
+ if (count($values) == 4) {
+ for ($i = 0; $i < 4; $i++) {
+ $return[$shorthands[$property][$i]] = $values[$i] . $important;
+ }
+ } elseif (count($values) == 3) {
+ $return[$shorthands[$property][0]] = $values[0] . $important;
+ $return[$shorthands[$property][1]] = $values[1] . $important;
+ $return[$shorthands[$property][3]] = $values[1] . $important;
+ $return[$shorthands[$property][2]] = $values[2] . $important;
+ } elseif (count($values) == 2) {
+ for ($i = 0; $i < 4; $i++) {
+ $return[$shorthands[$property][$i]] = (($i % 2 != 0)) ? $values[1] . $important : $values[0] . $important;
+ }
+ } else {
+ for ($i = 0; $i < 4; $i++) {
+ $return[$shorthands[$property][$i]] = $values[0] . $important;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Explodes a string as explode() does, however, not if $sep is escaped or within a string.
+ * @param string $sep seperator
+ * @param string $string
+ * @return array
+ * @version 1.0
+ */
+ static function explode_ws($sep, $string) {
+ $status = 'st';
+ $to = '';
+
+ $output = array();
+ $num = 0;
+ for ($i = 0, $len = strlen($string); $i < $len; $i++) {
+ switch ($status) {
+ case 'st':
+ if ($string{$i} == $sep && !csstidy::escaped($string, $i)) {
+ ++$num;
+ } elseif ($string{$i} === '"' || $string{$i} === '\'' || $string{$i} === '(' && !csstidy::escaped($string, $i)) {
+ $status = 'str';
+ $to = ($string{$i} === '(') ? ')' : $string{$i};
+ (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
+ } else {
+ (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
+ }
+ break;
+
+ case 'str':
+ if ($string{$i} == $to && !csstidy::escaped($string, $i)) {
+ $status = 'st';
+ }
+ (isset($output[$num])) ? $output[$num] .= $string{$i} : $output[$num] = $string{$i};
+ break;
+ }
+ }
+
+ if (isset($output[0])) {
+ return $output;
+ } else {
+ return array($output);
+ }
+ }
+
+ /**
+ * Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
+ * @param array $array
+ * @return array
+ * @version 1.2
+ * @see dissolve_4value_shorthands()
+ */
+ static function merge_4value_shorthands($array) {
+ $return = $array;
+ $shorthands = & $GLOBALS['csstidy']['shorthands'];
+
+ foreach ($shorthands as $key => $value) {
+ if (isset($array[$value[0]]) && isset($array[$value[1]])
+ && isset($array[$value[2]]) && isset($array[$value[3]]) && $value !== 0) {
+ $return[$key] = '';
+
+ $important = '';
+ for ($i = 0; $i < 4; $i++) {
+ $val = $array[$value[$i]];
+ if (csstidy::is_important($val)) {
+ $important = '!important';
+ $return[$key] .= csstidy::gvw_important($val) . ' ';
+ } else {
+ $return[$key] .= $val . ' ';
+ }
+ unset($return[$value[$i]]);
+ }
+ $return[$key] = csstidy_optimise::shorthand(trim($return[$key] . $important));
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Dissolve background property
+ * @param string $str_value
+ * @return array
+ * @version 1.0
+ * @see merge_bg()
+ * @todo full CSS 3 compliance
+ */
+ static function dissolve_short_bg($str_value) {
+ // don't try to explose background gradient !
+ if (stripos($str_value, "gradient(")!==FALSE)
+ return array('background'=>$str_value);
+
+ $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
+ $repeat = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space');
+ $attachment = array('scroll', 'fixed', 'local');
+ $clip = array('border', 'padding');
+ $origin = array('border', 'padding', 'content');
+ $pos = array('top', 'center', 'bottom', 'left', 'right');
+ $important = '';
+ $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);
+
+ if (csstidy::is_important($str_value)) {
+ $important = ' !important';
+ $str_value = csstidy::gvw_important($str_value);
+ }
+
+ $str_value = csstidy_optimise::explode_ws(',', $str_value);
+ for ($i = 0; $i < count($str_value); $i++) {
+ $have['clip'] = false;
+ $have['pos'] = false;
+ $have['color'] = false;
+ $have['bg'] = false;
+
+ if (is_array($str_value[$i])) {
+ $str_value[$i] = $str_value[$i][0];
+ }
+ $str_value[$i] = csstidy_optimise::explode_ws(' ', trim($str_value[$i]));
+
+ for ($j = 0; $j < count($str_value[$i]); $j++) {
+ if ($have['bg'] === false && (substr($str_value[$i][$j], 0, 4) === 'url(' || $str_value[$i][$j] === 'none')) {
+ $return['background-image'] .= $str_value[$i][$j] . ',';
+ $have['bg'] = true;
+ } elseif (in_array($str_value[$i][$j], $repeat, true)) {
+ $return['background-repeat'] .= $str_value[$i][$j] . ',';
+ } elseif (in_array($str_value[$i][$j], $attachment, true)) {
+ $return['background-attachment'] .= $str_value[$i][$j] . ',';
+ } elseif (in_array($str_value[$i][$j], $clip, true) && !$have['clip']) {
+ $return['background-clip'] .= $str_value[$i][$j] . ',';
+ $have['clip'] = true;
+ } elseif (in_array($str_value[$i][$j], $origin, true)) {
+ $return['background-origin'] .= $str_value[$i][$j] . ',';
+ } elseif ($str_value[$i][$j]{0} === '(') {
+ $return['background-size'] .= substr($str_value[$i][$j], 1, -1) . ',';
+ } 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} === '.') {
+ $return['background-position'] .= $str_value[$i][$j];
+ if (!$have['pos'])
+ $return['background-position'] .= ' '; else
+ $return['background-position'].= ',';
+ $have['pos'] = true;
+ }
+ elseif (!$have['color']) {
+ $return['background-color'] .= $str_value[$i][$j] . ',';
+ $have['color'] = true;
+ }
+ }
+ }
+
+ foreach ($background_prop_default as $bg_prop => $default_value) {
+ if ($return[$bg_prop] !== null) {
+ $return[$bg_prop] = substr($return[$bg_prop], 0, -1) . $important;
+ }
+ else
+ $return[$bg_prop] = $default_value . $important;
+ }
+ return $return;
+ }
+
+ /**
+ * Merges all background properties
+ * @param array $input_css
+ * @return array
+ * @version 1.0
+ * @see dissolve_short_bg()
+ * @todo full CSS 3 compliance
+ */
+ static function merge_bg($input_css) {
+ $background_prop_default = & $GLOBALS['csstidy']['background_prop_default'];
+ // Max number of background images. CSS3 not yet fully implemented
+ $number_of_values = @max(count(csstidy_optimise::explode_ws(',', $input_css['background-image'])), count(csstidy_optimise::explode_ws(',', $input_css['background-color'])), 1);
+ // Array with background images to check if BG image exists
+ $bg_img_array = @csstidy_optimise::explode_ws(',', csstidy::gvw_important($input_css['background-image']));
+ $new_bg_value = '';
+ $important = '';
+
+ // if background properties is here and not empty, don't try anything
+ if (isset($input_css['background']) AND $input_css['background'])
+ return $input_css;
+
+ for ($i = 0; $i < $number_of_values; $i++) {
+ foreach ($background_prop_default as $bg_property => $default_value) {
+ // Skip if property does not exist
+ if (!isset($input_css[$bg_property])) {
+ continue;
+ }
+
+ $cur_value = $input_css[$bg_property];
+ // skip all optimisation if gradient() somewhere
+ if (stripos($cur_value, "gradient(")!==FALSE)
+ return $input_css;
+
+ // Skip some properties if there is no background image
+ if ((!isset($bg_img_array[$i]) || $bg_img_array[$i] === 'none')
+ && ($bg_property === 'background-size' || $bg_property === 'background-position'
+ || $bg_property === 'background-attachment' || $bg_property === 'background-repeat')) {
+ continue;
+ }
+
+ // Remove !important
+ if (csstidy::is_important($cur_value)) {
+ $important = ' !important';
+ $cur_value = csstidy::gvw_important($cur_value);
+ }
+
+ // Do not add default values
+ if ($cur_value === $default_value) {
+ continue;
+ }
+
+ $temp = csstidy_optimise::explode_ws(',', $cur_value);
+
+ if (isset($temp[$i])) {
+ if ($bg_property === 'background-size') {
+ $new_bg_value .= '(' . $temp[$i] . ') ';
+ } else {
+ $new_bg_value .= $temp[$i] . ' ';
+ }
+ }
+ }
+
+ $new_bg_value = trim($new_bg_value);
+ if ($i != $number_of_values - 1)
+ $new_bg_value .= ',';
+ }
+
+ // Delete all background-properties
+ foreach ($background_prop_default as $bg_property => $default_value) {
+ unset($input_css[$bg_property]);
+ }
+
+ // Add new background property
+ if ($new_bg_value !== '')
+ $input_css['background'] = $new_bg_value . $important;
+ elseif(isset ($input_css['background']))
+ $input_css['background'] = 'none';
+
+ return $input_css;
+ }
+
+ /**
+ * Dissolve font property
+ * @param string $str_value
+ * @return array
+ * @version 1.3
+ * @see merge_font()
+ */
+ static function dissolve_short_font($str_value) {
+ $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
+ $font_weight = array('normal', 'bold', 'bolder', 'lighter', 100, 200, 300, 400, 500, 600, 700, 800, 900);
+ $font_variant = array('normal', 'small-caps');
+ $font_style = array('normal', 'italic', 'oblique');
+ $important = '';
+ $return = array('font-style' => null, 'font-variant' => null, 'font-weight' => null, 'font-size' => null, 'line-height' => null, 'font-family' => null);
+
+ if (csstidy::is_important($str_value)) {
+ $important = '!important';
+ $str_value = csstidy::gvw_important($str_value);
+ }
+
+ $have['style'] = false;
+ $have['variant'] = false;
+ $have['weight'] = false;
+ $have['size'] = false;
+ // Detects if font-family consists of several words w/o quotes
+ $multiwords = false;
+
+ // Workaround with multiple font-family
+ $str_value = csstidy_optimise::explode_ws(',', trim($str_value));
+
+ $str_value[0] = csstidy_optimise::explode_ws(' ', trim($str_value[0]));
+
+ for ($j = 0; $j < count($str_value[0]); $j++) {
+ if ($have['weight'] === false && in_array($str_value[0][$j], $font_weight)) {
+ $return['font-weight'] = $str_value[0][$j];
+ $have['weight'] = true;
+ } elseif ($have['variant'] === false && in_array($str_value[0][$j], $font_variant)) {
+ $return['font-variant'] = $str_value[0][$j];
+ $have['variant'] = true;
+ } elseif ($have['style'] === false && in_array($str_value[0][$j], $font_style)) {
+ $return['font-style'] = $str_value[0][$j];
+ $have['style'] = true;
+ } elseif ($have['size'] === false && (is_numeric($str_value[0][$j]{0}) || $str_value[0][$j]{0} === null || $str_value[0][$j]{0} === '.')) {
+ $size = csstidy_optimise::explode_ws('/', trim($str_value[0][$j]));
+ $return['font-size'] = $size[0];
+ if (isset($size[1])) {
+ $return['line-height'] = $size[1];
+ } else {
+ $return['line-height'] = ''; // don't add 'normal' !
+ }
+ $have['size'] = true;
+ } else {
+ if (isset($return['font-family'])) {
+ $return['font-family'] .= ' ' . $str_value[0][$j];
+ $multiwords = true;
+ } else {
+ $return['font-family'] = $str_value[0][$j];
+ }
+ }
+ }
+ // add quotes if we have several qords in font-family
+ if ($multiwords !== false) {
+ $return['font-family'] = '"' . $return['font-family'] . '"';
+ }
+ $i = 1;
+ while (isset($str_value[$i])) {
+ $return['font-family'] .= ',' . trim($str_value[$i]);
+ $i++;
+ }
+
+ // Fix for 100 and more font-size
+ if ($have['size'] === false && isset($return['font-weight']) &&
+ is_numeric($return['font-weight']{0})) {
+ $return['font-size'] = $return['font-weight'];
+ unset($return['font-weight']);
+ }
+
+ foreach ($font_prop_default as $font_prop => $default_value) {
+ if ($return[$font_prop] !== null) {
+ $return[$font_prop] = $return[$font_prop] . $important;
+ }
+ else
+ $return[$font_prop] = $default_value . $important;
+ }
+ return $return;
+ }
+
+ /**
+ * Merges all fonts properties
+ * @param array $input_css
+ * @return array
+ * @version 1.3
+ * @see dissolve_short_font()
+ */
+ static function merge_font($input_css) {
+ $font_prop_default = & $GLOBALS['csstidy']['font_prop_default'];
+ $new_font_value = '';
+ $important = '';
+ // Skip if not font-family and font-size set
+ if (isset($input_css['font-family']) && isset($input_css['font-size'])) {
+ // fix several words in font-family - add quotes
+ if (isset($input_css['font-family'])) {
+ $families = explode(",", $input_css['font-family']);
+ $result_families = array();
+ foreach ($families as $family) {
+ $family = trim($family);
+ $len = strlen($family);
+ if (strpos($family, " ") &&
+ !(($family{0} == '"' && $family{$len - 1} == '"') ||
+ ($family{0} == "'" && $family{$len - 1} == "'"))) {
+ $family = '"' . $family . '"';
+ }
+ $result_families[] = $family;
+ }
+ $input_css['font-family'] = implode(",", $result_families);
+ }
+ foreach ($font_prop_default as $font_property => $default_value) {
+
+ // Skip if property does not exist
+ if (!isset($input_css[$font_property])) {
+ continue;
+ }
+
+ $cur_value = $input_css[$font_property];
+
+ // Skip if default value is used
+ if ($cur_value === $default_value) {
+ continue;
+ }
+
+ // Remove !important
+ if (csstidy::is_important($cur_value)) {
+ $important = '!important';
+ $cur_value = csstidy::gvw_important($cur_value);
+ }
+
+ $new_font_value .= $cur_value;
+ // Add delimiter
+ $new_font_value .= ( $font_property === 'font-size' &&
+ isset($input_css['line-height'])) ? '/' : ' ';
+ }
+
+ $new_font_value = trim($new_font_value);
+
+ // Delete all font-properties
+ foreach ($font_prop_default as $font_property => $default_value) {
+ if ($font_property!=='font' OR !$new_font_value)
+ unset($input_css[$font_property]);
+ }
+
+ // Add new font property
+ if ($new_font_value !== '') {
+ $input_css['font'] = $new_font_value . $important;
+ }
+ }
+
+ return $input_css;
+ }
+
+}
diff --git a/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php
new file mode 100644
index 00000000..56b95404
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/class.csstidy_print.php
@@ -0,0 +1,410 @@
+<?php
+/**
+ * CSSTidy - CSS Parser and Optimiser
+ *
+ * CSS Printing class
+ * This class prints CSS data generated by csstidy.
+ *
+ * Copyright 2005, 2006, 2007 Florian Schmitz
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2007
+ * @author Brett Zamir (brettz9 at yahoo dot com) 2007
+ * @author Cedric Morin (cedric at yterium dot com) 2010
+ */
+
+/**
+ * CSS Printing class
+ *
+ * This class prints CSS data generated by csstidy.
+ *
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2006
+ * @version 1.0.1
+ */
+class csstidy_print {
+
+ /**
+ * Saves the input CSS string
+ * @var string
+ * @access private
+ */
+ public $input_css = '';
+ /**
+ * Saves the formatted CSS string
+ * @var string
+ * @access public
+ */
+ public $output_css = '';
+ /**
+ * Saves the formatted CSS string (plain text)
+ * @var string
+ * @access public
+ */
+ public $output_css_plain = '';
+
+ /**
+ * Constructor
+ * @param array $css contains the class csstidy
+ * @access private
+ * @version 1.0
+ */
+ function __construct(&$css) {
+ $this->parser = & $css;
+ $this->css = & $css->css;
+ $this->template = & $css->template;
+ $this->tokens = & $css->tokens;
+ $this->charset = & $css->charset;
+ $this->import = & $css->import;
+ $this->namespace = & $css->namespace;
+ }
+
+ function csstidy_print(&$css) {
+ $this->__construct($css);
+ }
+
+ /**
+ * Resets output_css and output_css_plain (new css code)
+ * @access private
+ * @version 1.0
+ */
+ function _reset() {
+ $this->output_css = '';
+ $this->output_css_plain = '';
+ }
+
+ /**
+ * Returns the CSS code as plain text
+ * @param string $default_media default @media to add to selectors without any @media
+ * @return string
+ * @access public
+ * @version 1.0
+ */
+ function plain($default_media='') {
+ $this->_print(true, $default_media);
+ return $this->output_css_plain;
+ }
+
+ /**
+ * Returns the formatted CSS code
+ * @param string $default_media default @media to add to selectors without any @media
+ * @return string
+ * @access public
+ * @version 1.0
+ */
+ function formatted($default_media='') {
+ $this->_print(false, $default_media);
+ return $this->output_css;
+ }
+
+ /**
+ * Returns the formatted CSS code to make a complete webpage
+ * @param string $doctype shorthand for the document type
+ * @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet
+ * @param string $title title to be added in the head of the document
+ * @param string $lang two-letter language code to be added to the output
+ * @return string
+ * @access public
+ * @version 1.4
+ */
+ function formatted_page($doctype='xhtml1.1', $externalcss=true, $title='', $lang='en') {
+ switch ($doctype) {
+ case 'xhtml1.0strict':
+ $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
+ break;
+ case 'xhtml1.1':
+ default:
+ $doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
+ break;
+ }
+
+ $output = $cssparsed = '';
+ $this->output_css_plain = & $output;
+
+ $output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"';
+ $output .= ( $doctype === 'xhtml1.1') ? '>' : ' lang="' . $lang . '">';
+ $output .= "\n<head>\n <title>$title</title>";
+
+ if ($externalcss) {
+ $output .= "\n <style type=\"text/css\">\n";
+ $cssparsed = file_get_contents('cssparsed.css');
+ $output .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php
+ $output .= "\n</style>";
+ } else {
+ $output .= "\n" . ' <link rel="stylesheet" type="text/css" href="cssparsed.css" />';
+// }
+ }
+ $output .= "\n</head>\n<body><code id=\"copytext\">";
+ $output .= $this->formatted();
+ $output .= '</code>' . "\n" . '</body></html>';
+ return $this->output_css_plain;
+ }
+
+ /**
+ * Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain
+ * @param bool $plain plain text or not
+ * @param string $default_media default @media to add to selectors without any @media
+ * @access private
+ * @version 2.0
+ */
+ function _print($plain = false, $default_media='') {
+ if ($this->output_css && $this->output_css_plain) {
+ return;
+ }
+
+ $output = '';
+ if (!$this->parser->get_cfg('preserve_css')) {
+ $this->_convert_raw_css($default_media);
+ }
+
+ $template = & $this->template;
+
+ if ($plain) {
+ $template = array_map('strip_tags', $template);
+ }
+
+ if ($this->parser->get_cfg('timestamp')) {
+ array_unshift($this->tokens, array(COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date('r') . ' '));
+ }
+
+ if (!empty($this->charset)) {
+ $output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6];
+ }
+
+ if (!empty($this->import)) {
+ for ($i = 0, $size = count($this->import); $i < $size; $i++) {
+ $import_components = explode(' ', $this->import[$i]);
+ if (substr($import_components[0], 0, 4) === 'url(' && substr($import_components[0], -1, 1) === ')') {
+ $import_components[0] = '\'' . trim(substr($import_components[0], 4, -1), "'\"") . '\'';
+ $this->import[$i] = implode(' ', $import_components);
+ $this->parser->log('Optimised @import : Removed "url("', 'Information');
+ }
+ $output .= $template[0] . '@import ' . $template[5] . $this->import[$i] . $template[6];
+ }
+ }
+ if (!empty($this->namespace)) {
+ if (substr($this->namespace, 0, 4) === 'url(' && substr($this->namespace, -1, 1) === ')') {
+ $this->namespace = '\'' . substr($this->namespace, 4, -1) . '\'';
+ $this->parser->log('Optimised @namespace : Removed "url("', 'Information');
+ }
+ $output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6];
+ }
+
+ $output .= $template[13];
+ $in_at_out = '';
+ $out = & $output;
+
+ foreach ($this->tokens as $key => $token) {
+ switch ($token[0]) {
+ case AT_START:
+ $out .= $template[0] . $this->_htmlsp($token[1], $plain) . $template[1];
+ $out = & $in_at_out;
+ break;
+
+ case SEL_START:
+ if ($this->parser->get_cfg('lowercase_s'))
+ $token[1] = strtolower($token[1]);
+ $out .= ( $token[1]{0} !== '@') ? $template[2] . $this->_htmlsp($token[1], $plain) : $template[0] . $this->_htmlsp($token[1], $plain);
+ $out .= $template[3];
+ break;
+
+ case PROPERTY:
+ if ($this->parser->get_cfg('case_properties') === 2) {
+ $token[1] = strtoupper($token[1]);
+ } elseif ($this->parser->get_cfg('case_properties') === 1) {
+ $token[1] = strtolower($token[1]);
+ }
+ $out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5];
+ break;
+
+ case VALUE:
+ $out .= $this->_htmlsp($token[1], $plain);
+ if ($this->_seeknocomment($key, 1) == SEL_END && $this->parser->get_cfg('remove_last_;')) {
+ $out .= str_replace(';', '', $template[6]);
+ } else {
+ $out .= $template[6];
+ }
+ break;
+
+ case SEL_END:
+ $out .= $template[7];
+ if ($this->_seeknocomment($key, 1) != AT_END)
+ $out .= $template[8];
+ break;
+
+ case AT_END:
+ $out = & $output;
+ $out .= $template[10] . str_replace("\n", "\n" . $template[10], $in_at_out);
+ $in_at_out = '';
+ $out .= $template[9];
+ break;
+
+ case COMMENT:
+ $out .= $template[11] . '/*' . $this->_htmlsp($token[1], $plain) . '*/' . $template[12];
+ break;
+ }
+ }
+
+ $output = trim($output);
+
+ if (!$plain) {
+ $this->output_css = $output;
+ $this->_print(true);
+ } else {
+ // If using spaces in the template, don't want these to appear in the plain output
+ $this->output_css_plain = str_replace('&#160;', '', $output);
+ }
+ }
+
+ /**
+ * Gets the next token type which is $move away from $key, excluding comments
+ * @param integer $key current position
+ * @param integer $move move this far
+ * @return mixed a token type
+ * @access private
+ * @version 1.0
+ */
+ function _seeknocomment($key, $move) {
+ $go = ($move > 0) ? 1 : -1;
+ for ($i = $key + 1; abs($key - $i) - 1 < abs($move); $i += $go) {
+ if (!isset($this->tokens[$i])) {
+ return;
+ }
+ if ($this->tokens[$i][0] == COMMENT) {
+ $move += 1;
+ continue;
+ }
+ return $this->tokens[$i][0];
+ }
+ }
+
+ /**
+ * Converts $this->css array to a raw array ($this->tokens)
+ * @param string $default_media default @media to add to selectors without any @media
+ * @access private
+ * @version 1.0
+ */
+ function _convert_raw_css($default_media='') {
+ $this->tokens = array();
+
+ foreach ($this->css as $medium => $val) {
+ if ($this->parser->get_cfg('sort_selectors'))
+ ksort($val);
+ if (intval($medium) < DEFAULT_AT) {
+ $this->parser->_add_token(AT_START, $medium, true);
+ }
+ elseif ($default_media) {
+ $this->parser->_add_token(AT_START, $default_media, true);
+ }
+
+ foreach ($val as $selector => $vali) {
+ if ($this->parser->get_cfg('sort_properties'))
+ ksort($vali);
+ $this->parser->_add_token(SEL_START, $selector, true);
+
+ foreach ($vali as $property => $valj) {
+ $this->parser->_add_token(PROPERTY, $property, true);
+ $this->parser->_add_token(VALUE, $valj, true);
+ }
+
+ $this->parser->_add_token(SEL_END, $selector, true);
+ }
+
+ if (intval($medium) < DEFAULT_AT) {
+ $this->parser->_add_token(AT_END, $medium, true);
+ }
+ elseif ($default_media) {
+ $this->parser->_add_token(AT_END, $default_media, true);
+ }
+ }
+ }
+
+ /**
+ * Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner.
+ * @param string $string
+ * @param bool $plain
+ * @return string
+ * @see csstidy_print::_print()
+ * @access private
+ * @version 1.0
+ */
+ function _htmlsp($string, $plain) {
+ if (!$plain) {
+ return htmlspecialchars($string, ENT_QUOTES, 'utf-8');
+ }
+ return $string;
+ }
+
+ /**
+ * Get compression ratio
+ * @access public
+ * @return float
+ * @version 1.2
+ */
+ function get_ratio() {
+ if (!$this->output_css_plain) {
+ $this->formatted();
+ }
+ return round((strlen($this->input_css) - strlen($this->output_css_plain)) / strlen($this->input_css), 3) * 100;
+ }
+
+ /**
+ * Get difference between the old and new code in bytes and prints the code if necessary.
+ * @access public
+ * @return string
+ * @version 1.1
+ */
+ function get_diff() {
+ if (!$this->output_css_plain) {
+ $this->formatted();
+ }
+
+ $diff = strlen($this->output_css_plain) - strlen($this->input_css);
+
+ if ($diff > 0) {
+ return '+' . $diff;
+ } elseif ($diff == 0) {
+ return '+-' . $diff;
+ }
+
+ return $diff;
+ }
+
+ /**
+ * Get the size of either input or output CSS in KB
+ * @param string $loc default is "output"
+ * @access public
+ * @return integer
+ * @version 1.0
+ */
+ function size($loc = 'output') {
+ if ($loc === 'output' && !$this->output_css) {
+ $this->formatted();
+ }
+
+ if ($loc === 'input') {
+ return (strlen($this->input_css) / 1000);
+ } else {
+ return (strlen($this->output_css_plain) / 1000);
+ }
+ }
+
+}
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.css b/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.css
new file mode 100644
index 00000000..54a7a9c9
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.css
@@ -0,0 +1,119 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+@import url("modules/custom-css/csstidy/cssparsed.css");
+
+html, body {
+font:0.8em Verdana,Helvetica,sans-serif;
+background:#F8F8F6;
+}
+
+code {
+font-size:1.2em;
+}
+
+div#rightcol {
+padding-right:32em;
+}
+
+fieldset {
+display:block;
+margin:0.5em 0;
+padding:1em;
+border:solid #7284AB 2px;
+}
+fieldset.code_output {
+display:inline;
+}
+
+h1 {
+font-size:2em;
+}
+
+small {
+font-size:0.7em;
+}
+
+fieldset#field_input {
+float:right;
+margin:0 0 1em 0.5em;
+}
+
+fieldset#options,fieldset#code_layout {
+width:31em;
+}
+
+input#submit {
+clear:both;
+display:block;
+margin:1em;
+}
+
+select {
+margin:2px 0 0;
+}
+
+label.block {
+display:block;
+}
+
+legend {
+background:#c4E1C3;
+padding:2px 4px;
+border:dashed 1px;
+}
+
+textarea#css_text {
+width:27em;
+height:370px;
+display:block;
+margin-left:1em;
+}
+
+.help {
+cursor:help;
+}
+
+p.important {
+border:solid 1px red;
+font-weight:bold;
+padding:1em;
+background:white;
+}
+
+p {
+margin:1em 0;
+}
+
+dl {
+padding-right:0.5em;
+}
+
+dt {
+font-weight:bold;
+margin:0;
+float:right;
+clear:both;
+height:1.5em;
+}
+
+dd {
+margin:0 4em 0 0;
+height:1.5em;
+}
+
+fieldset#messages {
+background:white;
+padding:0 1em 0 0;
+}
+
+fieldset#messages div {
+height:10em;
+overflow:auto;
+}
+
+dd.Warning {
+color:orange;
+}
+
+dd.Information {
+color:green;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.min.css b/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.min.css
new file mode 100644
index 00000000..02da7f4c
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparse-rtl.min.css
@@ -0,0 +1 @@
+code#copytext{white-space:pre;font-family:Verdana}.at{color:#00008b}.format{color:gray}.property{color:green}.selector{color:#00f}.value{color:red;left:500px}.comment{color:orange}body,html{font:.8em Verdana,Helvetica,sans-serif;background:#f8f8f6}code{font-size:1.2em}div#rightcol{padding-right:32em}fieldset{display:block;margin:.5em 0;padding:1em;border:solid #7284ab 2px}fieldset.code_output{display:inline}h1{font-size:2em}small{font-size:.7em}fieldset#field_input{float:right;margin:0 0 1em .5em}fieldset#code_layout,fieldset#options{width:31em}input#submit{clear:both;display:block;margin:1em}select{margin:2px 0 0}label.block{display:block}legend{background:#c4e1c3;padding:2px 4px;border:dashed 1px}textarea#css_text{width:27em;height:370px;display:block;margin-left:1em}.help{cursor:help}p.important{border:solid 1px red;font-weight:700;padding:1em;background:#fff}p{margin:1em 0}dl{padding-right:.5em}dt{font-weight:700;margin:0;float:right;clear:both;height:1.5em}dd{margin:0 4em 0 0;height:1.5em}fieldset#messages{background:#fff;padding:0 1em 0 0}fieldset#messages div{height:10em;overflow:auto}dd.Warning{color:orange}dd.Information{color:green} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparse.css b/plugins/jetpack/modules/custom-css/csstidy/cssparse.css
new file mode 100644
index 00000000..bddd34f9
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparse.css
@@ -0,0 +1,118 @@
+@import url("modules/custom-css/csstidy/cssparsed.css");
+
+html, body {
+font:0.8em Verdana,Helvetica,sans-serif;
+background:#F8F8F6;
+}
+
+code {
+font-size:1.2em;
+}
+
+div#rightcol {
+padding-left:32em;
+}
+
+fieldset {
+display:block;
+margin:0.5em 0;
+padding:1em;
+border:solid #7284AB 2px;
+}
+fieldset.code_output {
+display:inline;
+}
+
+h1 {
+font-size:2em;
+}
+
+small {
+font-size:0.7em;
+}
+
+fieldset#field_input {
+float:left;
+margin:0 0.5em 1em 0;
+}
+
+fieldset#options,fieldset#code_layout {
+width:31em;
+}
+
+input#submit {
+clear:both;
+display:block;
+margin:1em;
+}
+
+select {
+margin:2px 0 0;
+}
+
+label.block {
+display:block;
+}
+
+legend {
+background:#c4E1C3;
+padding:2px 4px;
+border:dashed 1px;
+}
+
+textarea#css_text {
+width:27em;
+height:370px;
+display:block;
+margin-right:1em;
+}
+
+.help {
+cursor:help;
+}
+
+p.important {
+border:solid 1px red;
+font-weight:bold;
+padding:1em;
+background:white;
+}
+
+p {
+margin:1em 0;
+}
+
+dl {
+padding-left:0.5em;
+}
+
+dt {
+font-weight:bold;
+margin:0;
+float:left;
+clear:both;
+height:1.5em;
+}
+
+dd {
+margin:0 0 0 4em;
+height:1.5em;
+}
+
+fieldset#messages {
+background:white;
+padding:0 0 0 1em;
+}
+
+fieldset#messages div {
+height:10em;
+overflow:auto;
+}
+
+dd.Warning {
+color:orange;
+}
+
+dd.Information {
+color:green;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparse.min.css b/plugins/jetpack/modules/custom-css/csstidy/cssparse.min.css
new file mode 100644
index 00000000..fa4927e5
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparse.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+code#copytext{white-space:pre;font-family:Verdana}.at{color:#00008b}.format{color:gray}.property{color:green}.selector{color:#00f}.value{color:red;left:500px}.comment{color:orange}body,html{font:.8em Verdana,Helvetica,sans-serif;background:#f8f8f6}code{font-size:1.2em}div#rightcol{padding-left:32em}fieldset{display:block;margin:.5em 0;padding:1em;border:solid #7284ab 2px}fieldset.code_output{display:inline}h1{font-size:2em}small{font-size:.7em}fieldset#field_input{float:left;margin:0 .5em 1em 0}fieldset#code_layout,fieldset#options{width:31em}input#submit{clear:both;display:block;margin:1em}select{margin:2px 0 0}label.block{display:block}legend{background:#c4e1c3;padding:2px 4px;border:dashed 1px}textarea#css_text{width:27em;height:370px;display:block;margin-right:1em}.help{cursor:help}p.important{border:solid 1px red;font-weight:700;padding:1em;background:#fff}p{margin:1em 0}dl{padding-left:.5em}dt{font-weight:700;margin:0;float:left;clear:both;height:1.5em}dd{margin:0 0 0 4em;height:1.5em}fieldset#messages{background:#fff;padding:0 0 0 1em}fieldset#messages div{height:10em;overflow:auto}dd.Warning{color:orange}dd.Information{color:green} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.css b/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.css
new file mode 100644
index 00000000..d765e2d7
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.css
@@ -0,0 +1,30 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+code#copytext {
+ white-space: pre;
+ font-family: Verdana;
+}
+
+.at {
+color:darkblue;
+}
+
+.format {
+color:gray;
+}
+
+.property {
+color:green;
+}
+
+.selector {
+color:blue;
+}
+
+.value {
+color:red;
+right: 500px;
+}
+
+.comment {
+color:orange;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.min.css b/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.min.css
new file mode 100644
index 00000000..ba9903d0
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparsed-rtl.min.css
@@ -0,0 +1 @@
+code#copytext{white-space:pre;font-family:Verdana}.at{color:#00008b}.format{color:gray}.property{color:green}.selector{color:#00f}.value{color:red;right:500px}.comment{color:orange} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css
new file mode 100644
index 00000000..5aaf2bb1
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.css
@@ -0,0 +1,29 @@
+code#copytext {
+ white-space: pre;
+ font-family: Verdana;
+}
+
+.at {
+color:darkblue;
+}
+
+.format {
+color:gray;
+}
+
+.property {
+color:green;
+}
+
+.selector {
+color:blue;
+}
+
+.value {
+color:red;
+left: 500px;
+}
+
+.comment {
+color:orange;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/cssparsed.min.css b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.min.css
new file mode 100644
index 00000000..db96935e
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/cssparsed.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+code#copytext{white-space:pre;font-family:Verdana}.at{color:#00008b}.format{color:gray}.property{color:green}.selector{color:#00f}.value{color:red;left:500px}.comment{color:orange} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php b/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php
new file mode 100644
index 00000000..f0f7376f
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/data-wp.inc.php
@@ -0,0 +1,102 @@
+<?php
+
+unset( $GLOBALS['csstidy']['all_properties']['binding'] );
+
+$GLOBALS['csstidy']['all_properties']['text-size-adjust'] = 'CSS3.0';
+
+// Support browser prefixes for properties only in the latest CSS draft
+foreach ( $GLOBALS['csstidy']['all_properties'] as $property => $levels ) {
+ if ( strpos( $levels, "," ) === false ) {
+ $GLOBALS['csstidy']['all_properties']['-moz-' . $property] = $levels;
+ $GLOBALS['csstidy']['all_properties']['-webkit-' . $property] = $levels;
+ $GLOBALS['csstidy']['all_properties']['-ms-' . $property] = $levels;
+ $GLOBALS['csstidy']['all_properties']['-o-' . $property] = $levels;
+ $GLOBALS['csstidy']['all_properties']['-khtml-' . $property] = $levels;
+
+ if ( in_array( $property, $GLOBALS['csstidy']['unit_values'] ) ) {
+ $GLOBALS['csstidy']['unit_values'][] = '-moz-' . $property;
+ $GLOBALS['csstidy']['unit_values'][] = '-webkit-' . $property;
+ $GLOBALS['csstidy']['unit_values'][] = '-ms-' . $property;
+ $GLOBALS['csstidy']['unit_values'][] = '-o-' . $property;
+ $GLOBALS['csstidy']['unit_values'][] = '-khtml-' . $property;
+ }
+
+ if ( in_array( $property, $GLOBALS['csstidy']['color_values'] ) ) {
+ $GLOBALS['csstidy']['color_values'][] = '-moz-' . $property;
+ $GLOBALS['csstidy']['color_values'][] = '-webkit-' . $property;
+ $GLOBALS['csstidy']['color_values'][] = '-ms-' . $property;
+ $GLOBALS['csstidy']['color_values'][] = '-o-' . $property;
+ $GLOBALS['csstidy']['color_values'][] = '-khtml-' . $property;
+ }
+ }
+}
+
+// Add `display` to the list of properties that can be used multiple times in a single selector
+$GLOBALS['csstidy']['multiple_properties'][] = 'display';
+
+// Allow vendor prefixes for any property that is allowed to be used multiple times inside a single selector
+foreach ( $GLOBALS['csstidy']['multiple_properties'] as $property ) {
+ if ( '-' != $property[0] ) {
+ $GLOBALS['csstidy']['multiple_properties'][] = '-o-' . $property;
+ $GLOBALS['csstidy']['multiple_properties'][] = '-ms-' . $property;
+ $GLOBALS['csstidy']['multiple_properties'][] = '-webkit-' . $property;
+ $GLOBALS['csstidy']['multiple_properties'][] = '-moz-' . $property;
+ $GLOBALS['csstidy']['multiple_properties'][] = '-khtml-' . $property;
+ }
+}
+
+/**
+ * CSS Animation
+ *
+ * @see https://developer.mozilla.org/en/CSS/CSS_animations
+ */
+$GLOBALS['csstidy']['at_rules']['-webkit-keyframes'] = 'at';
+$GLOBALS['csstidy']['at_rules']['-moz-keyframes'] = 'at';
+$GLOBALS['csstidy']['at_rules']['-ms-keyframes'] = 'at';
+$GLOBALS['csstidy']['at_rules']['-o-keyframes'] = 'at';
+
+/**
+ * Non-standard viewport rule.
+ */
+$GLOBALS['csstidy']['at_rules']['viewport'] = 'is';
+$GLOBALS['csstidy']['at_rules']['-webkit-viewport'] = 'is';
+$GLOBALS['csstidy']['at_rules']['-moz-viewport'] = 'is';
+$GLOBALS['csstidy']['at_rules']['-ms-viewport'] = 'is';
+
+/**
+ * Non-standard CSS properties. They're not part of any spec, but we say
+ * they're in all of them so that we can support them.
+ */
+$GLOBALS['csstidy']['all_properties']['-webkit-filter'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-moz-filter'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-ms-filter'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['filter'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['scrollbar-face-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-ms-interpolation-mode'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-rendering'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-x'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-y'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-webkit-transform-origin-z'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-webkit-font-smoothing'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-moz-osx-font-smoothing'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-font-smooth'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-o-object-fit'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['object-fit'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['-o-object-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['object-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-overflow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['zoom'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pointer-events'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-feature-settings'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-kerning'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-language-override'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-synthesis'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-alternates'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-caps'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-east-asian'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-ligatures'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-numeric'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variation-settings'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-height-step'] = 'CSS3.0';
+
diff --git a/plugins/jetpack/modules/custom-css/csstidy/data.inc.php b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php
new file mode 100644
index 00000000..9ed2e1c4
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/data.inc.php
@@ -0,0 +1,693 @@
+<?php
+/**
+ * Various CSS Data for CSSTidy
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CSSTidy; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005
+ * @author Nikolay Matsievsky (speed at webo dot name) 2010
+ */
+
+define('AT_START', 1);
+define('AT_END', 2);
+define('SEL_START', 3);
+define('SEL_END', 4);
+define('PROPERTY', 5);
+define('VALUE', 6);
+define('COMMENT', 7);
+define('DEFAULT_AT', 41);
+
+/**
+ * All whitespace allowed in CSS
+ *
+ * @global array $GLOBALS['csstidy']['whitespace']
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['whitespace'] = array(' ',"\n","\t","\r","\x0B");
+
+/**
+ * All CSS tokens used by csstidy
+ *
+ * @global string $GLOBALS['csstidy']['tokens']
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['tokens'] = '/@}{;:=\'"(,\\!$%&)*+.<>?[]^`|~';
+
+/**
+ * All CSS units (CSS 3 units included)
+ *
+ * @see compress_numbers()
+ * @global array $GLOBALS['csstidy']['units']
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['units'] = array('in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz');
+
+/**
+ * Available at-rules
+ *
+ * @global array $GLOBALS['csstidy']['at_rules']
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['at_rules'] = array('page' => 'is','font-face' => 'is','charset' => 'iv', 'import' => 'iv','namespace' => 'iv','media' => 'at','keyframes' => 'at', 'supports' => 'at');
+
+ /**
+ * Properties that need a value with unit
+ *
+ * @todo CSS3 properties
+ * @see compress_numbers();
+ * @global array $GLOBALS['csstidy']['unit_values']
+ * @version 1.2
+ */
+$GLOBALS['csstidy']['unit_values'] = array ('background', 'background-position', 'background-size', 'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-width',
+ 'border-top-width', 'border-right-width', 'border-left-width', 'border-bottom-width', 'bottom', 'border-spacing', 'column-gap', 'column-width',
+ 'font-size', 'height', 'left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'max-height',
+ 'max-width', 'min-height', 'min-width', 'outline', 'outline-width', 'padding', 'padding-top', 'padding-right',
+ 'padding-bottom', 'padding-left', 'perspective', 'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width');
+
+/**
+ * Properties that allow <color> as value
+ *
+ * @todo CSS3 properties
+ * @see compress_numbers();
+ * @global array $GLOBALS['csstidy']['color_values']
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['color_values'] = array();
+$GLOBALS['csstidy']['color_values'][] = 'background-color';
+$GLOBALS['csstidy']['color_values'][] = 'border-color';
+$GLOBALS['csstidy']['color_values'][] = 'border-top-color';
+$GLOBALS['csstidy']['color_values'][] = 'border-right-color';
+$GLOBALS['csstidy']['color_values'][] = 'border-bottom-color';
+$GLOBALS['csstidy']['color_values'][] = 'border-left-color';
+$GLOBALS['csstidy']['color_values'][] = 'color';
+$GLOBALS['csstidy']['color_values'][] = 'outline-color';
+$GLOBALS['csstidy']['color_values'][] = 'column-rule-color';
+
+/**
+ * Default values for the background properties
+ *
+ * @todo Possibly property names will change during CSS3 development
+ * @global array $GLOBALS['csstidy']['background_prop_default']
+ * @see dissolve_short_bg()
+ * @see merge_bg()
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['background_prop_default'] = array();
+$GLOBALS['csstidy']['background_prop_default']['background-image'] = 'none';
+$GLOBALS['csstidy']['background_prop_default']['background-size'] = 'auto';
+$GLOBALS['csstidy']['background_prop_default']['background-repeat'] = 'repeat';
+$GLOBALS['csstidy']['background_prop_default']['background-position'] = '0 0';
+$GLOBALS['csstidy']['background_prop_default']['background-attachment'] = 'scroll';
+$GLOBALS['csstidy']['background_prop_default']['background-clip'] = 'border';
+$GLOBALS['csstidy']['background_prop_default']['background-origin'] = 'padding';
+$GLOBALS['csstidy']['background_prop_default']['background-color'] = 'transparent';
+
+/**
+ * Default values for the font properties
+ *
+ * @global array $GLOBALS['csstidy']['font_prop_default']
+ * @see merge_fonts()
+ * @version 1.3
+ */
+$GLOBALS['csstidy']['font_prop_default'] = array();
+$GLOBALS['csstidy']['font_prop_default']['font-style'] = 'normal';
+$GLOBALS['csstidy']['font_prop_default']['font-variant'] = 'normal';
+$GLOBALS['csstidy']['font_prop_default']['font-weight'] = 'normal';
+$GLOBALS['csstidy']['font_prop_default']['font-size'] = '';
+$GLOBALS['csstidy']['font_prop_default']['line-height'] = '';
+$GLOBALS['csstidy']['font_prop_default']['font-family'] = '';
+
+/**
+ * A list of non-W3C color names which get replaced by their hex-codes
+ *
+ * @global array $GLOBALS['csstidy']['replace_colors']
+ * @see cut_color()
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['replace_colors'] = array();
+$GLOBALS['csstidy']['replace_colors']['aliceblue'] = '#f0f8ff';
+$GLOBALS['csstidy']['replace_colors']['antiquewhite'] = '#faebd7';
+$GLOBALS['csstidy']['replace_colors']['aquamarine'] = '#7fffd4';
+$GLOBALS['csstidy']['replace_colors']['azure'] = '#f0ffff';
+$GLOBALS['csstidy']['replace_colors']['beige'] = '#f5f5dc';
+$GLOBALS['csstidy']['replace_colors']['bisque'] = '#ffe4c4';
+$GLOBALS['csstidy']['replace_colors']['blanchedalmond'] = '#ffebcd';
+$GLOBALS['csstidy']['replace_colors']['blueviolet'] = '#8a2be2';
+$GLOBALS['csstidy']['replace_colors']['brown'] = '#a52a2a';
+$GLOBALS['csstidy']['replace_colors']['burlywood'] = '#deb887';
+$GLOBALS['csstidy']['replace_colors']['cadetblue'] = '#5f9ea0';
+$GLOBALS['csstidy']['replace_colors']['chartreuse'] = '#7fff00';
+$GLOBALS['csstidy']['replace_colors']['chocolate'] = '#d2691e';
+$GLOBALS['csstidy']['replace_colors']['coral'] = '#ff7f50';
+$GLOBALS['csstidy']['replace_colors']['cornflowerblue'] = '#6495ed';
+$GLOBALS['csstidy']['replace_colors']['cornsilk'] = '#fff8dc';
+$GLOBALS['csstidy']['replace_colors']['crimson'] = '#dc143c';
+$GLOBALS['csstidy']['replace_colors']['cyan'] = '#00ffff';
+$GLOBALS['csstidy']['replace_colors']['darkblue'] = '#00008b';
+$GLOBALS['csstidy']['replace_colors']['darkcyan'] = '#008b8b';
+$GLOBALS['csstidy']['replace_colors']['darkgoldenrod'] = '#b8860b';
+$GLOBALS['csstidy']['replace_colors']['darkgray'] = '#a9a9a9';
+$GLOBALS['csstidy']['replace_colors']['darkgreen'] = '#006400';
+$GLOBALS['csstidy']['replace_colors']['darkkhaki'] = '#bdb76b';
+$GLOBALS['csstidy']['replace_colors']['darkmagenta'] = '#8b008b';
+$GLOBALS['csstidy']['replace_colors']['darkolivegreen'] = '#556b2f';
+$GLOBALS['csstidy']['replace_colors']['darkorange'] = '#ff8c00';
+$GLOBALS['csstidy']['replace_colors']['darkorchid'] = '#9932cc';
+$GLOBALS['csstidy']['replace_colors']['darkred'] = '#8b0000';
+$GLOBALS['csstidy']['replace_colors']['darksalmon'] = '#e9967a';
+$GLOBALS['csstidy']['replace_colors']['darkseagreen'] = '#8fbc8f';
+$GLOBALS['csstidy']['replace_colors']['darkslateblue'] = '#483d8b';
+$GLOBALS['csstidy']['replace_colors']['darkslategray'] = '#2f4f4f';
+$GLOBALS['csstidy']['replace_colors']['darkturquoise'] = '#00ced1';
+$GLOBALS['csstidy']['replace_colors']['darkviolet'] = '#9400d3';
+$GLOBALS['csstidy']['replace_colors']['deeppink'] = '#ff1493';
+$GLOBALS['csstidy']['replace_colors']['deepskyblue'] = '#00bfff';
+$GLOBALS['csstidy']['replace_colors']['dimgray'] = '#696969';
+$GLOBALS['csstidy']['replace_colors']['dodgerblue'] = '#1e90ff';
+$GLOBALS['csstidy']['replace_colors']['feldspar'] = '#d19275';
+$GLOBALS['csstidy']['replace_colors']['firebrick'] = '#b22222';
+$GLOBALS['csstidy']['replace_colors']['floralwhite'] = '#fffaf0';
+$GLOBALS['csstidy']['replace_colors']['forestgreen'] = '#228b22';
+$GLOBALS['csstidy']['replace_colors']['gainsboro'] = '#dcdcdc';
+$GLOBALS['csstidy']['replace_colors']['ghostwhite'] = '#f8f8ff';
+$GLOBALS['csstidy']['replace_colors']['gold'] = '#ffd700';
+$GLOBALS['csstidy']['replace_colors']['goldenrod'] = '#daa520';
+$GLOBALS['csstidy']['replace_colors']['greenyellow'] = '#adff2f';
+$GLOBALS['csstidy']['replace_colors']['honeydew'] = '#f0fff0';
+$GLOBALS['csstidy']['replace_colors']['hotpink'] = '#ff69b4';
+$GLOBALS['csstidy']['replace_colors']['indianred'] = '#cd5c5c';
+$GLOBALS['csstidy']['replace_colors']['indigo'] = '#4b0082';
+$GLOBALS['csstidy']['replace_colors']['ivory'] = '#fffff0';
+$GLOBALS['csstidy']['replace_colors']['khaki'] = '#f0e68c';
+$GLOBALS['csstidy']['replace_colors']['lavender'] = '#e6e6fa';
+$GLOBALS['csstidy']['replace_colors']['lavenderblush'] = '#fff0f5';
+$GLOBALS['csstidy']['replace_colors']['lawngreen'] = '#7cfc00';
+$GLOBALS['csstidy']['replace_colors']['lemonchiffon'] = '#fffacd';
+$GLOBALS['csstidy']['replace_colors']['lightblue'] = '#add8e6';
+$GLOBALS['csstidy']['replace_colors']['lightcoral'] = '#f08080';
+$GLOBALS['csstidy']['replace_colors']['lightcyan'] = '#e0ffff';
+$GLOBALS['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#fafad2';
+$GLOBALS['csstidy']['replace_colors']['lightgrey'] = '#d3d3d3';
+$GLOBALS['csstidy']['replace_colors']['lightgreen'] = '#90ee90';
+$GLOBALS['csstidy']['replace_colors']['lightpink'] = '#ffb6c1';
+$GLOBALS['csstidy']['replace_colors']['lightsalmon'] = '#ffa07a';
+$GLOBALS['csstidy']['replace_colors']['lightseagreen'] = '#20b2aa';
+$GLOBALS['csstidy']['replace_colors']['lightskyblue'] = '#87cefa';
+$GLOBALS['csstidy']['replace_colors']['lightslateblue'] = '#8470ff';
+$GLOBALS['csstidy']['replace_colors']['lightslategray'] = '#778899';
+$GLOBALS['csstidy']['replace_colors']['lightsteelblue'] = '#b0c4de';
+$GLOBALS['csstidy']['replace_colors']['lightyellow'] = '#ffffe0';
+$GLOBALS['csstidy']['replace_colors']['limegreen'] = '#32cd32';
+$GLOBALS['csstidy']['replace_colors']['linen'] = '#faf0e6';
+$GLOBALS['csstidy']['replace_colors']['magenta'] = '#ff00ff';
+$GLOBALS['csstidy']['replace_colors']['mediumaquamarine'] = '#66cdaa';
+$GLOBALS['csstidy']['replace_colors']['mediumblue'] = '#0000cd';
+$GLOBALS['csstidy']['replace_colors']['mediumorchid'] = '#ba55d3';
+$GLOBALS['csstidy']['replace_colors']['mediumpurple'] = '#9370d8';
+$GLOBALS['csstidy']['replace_colors']['mediumseagreen'] = '#3cb371';
+$GLOBALS['csstidy']['replace_colors']['mediumslateblue'] = '#7b68ee';
+$GLOBALS['csstidy']['replace_colors']['mediumspringgreen'] = '#00fa9a';
+$GLOBALS['csstidy']['replace_colors']['mediumturquoise'] = '#48d1cc';
+$GLOBALS['csstidy']['replace_colors']['mediumvioletred'] = '#c71585';
+$GLOBALS['csstidy']['replace_colors']['midnightblue'] = '#191970';
+$GLOBALS['csstidy']['replace_colors']['mintcream'] = '#f5fffa';
+$GLOBALS['csstidy']['replace_colors']['mistyrose'] = '#ffe4e1';
+$GLOBALS['csstidy']['replace_colors']['moccasin'] = '#ffe4b5';
+$GLOBALS['csstidy']['replace_colors']['navajowhite'] = '#ffdead';
+$GLOBALS['csstidy']['replace_colors']['oldlace'] = '#fdf5e6';
+$GLOBALS['csstidy']['replace_colors']['olivedrab'] = '#6b8e23';
+$GLOBALS['csstidy']['replace_colors']['orangered'] = '#ff4500';
+$GLOBALS['csstidy']['replace_colors']['orchid'] = '#da70d6';
+$GLOBALS['csstidy']['replace_colors']['palegoldenrod'] = '#eee8aa';
+$GLOBALS['csstidy']['replace_colors']['palegreen'] = '#98fb98';
+$GLOBALS['csstidy']['replace_colors']['paleturquoise'] = '#afeeee';
+$GLOBALS['csstidy']['replace_colors']['palevioletred'] = '#d87093';
+$GLOBALS['csstidy']['replace_colors']['papayawhip'] = '#ffefd5';
+$GLOBALS['csstidy']['replace_colors']['peachpuff'] = '#ffdab9';
+$GLOBALS['csstidy']['replace_colors']['peru'] = '#cd853f';
+$GLOBALS['csstidy']['replace_colors']['pink'] = '#ffc0cb';
+$GLOBALS['csstidy']['replace_colors']['plum'] = '#dda0dd';
+$GLOBALS['csstidy']['replace_colors']['powderblue'] = '#b0e0e6';
+$GLOBALS['csstidy']['replace_colors']['rosybrown'] = '#bc8f8f';
+$GLOBALS['csstidy']['replace_colors']['royalblue'] = '#4169e1';
+$GLOBALS['csstidy']['replace_colors']['saddlebrown'] = '#8b4513';
+$GLOBALS['csstidy']['replace_colors']['salmon'] = '#fa8072';
+$GLOBALS['csstidy']['replace_colors']['sandybrown'] = '#f4a460';
+$GLOBALS['csstidy']['replace_colors']['seagreen'] = '#2e8b57';
+$GLOBALS['csstidy']['replace_colors']['seashell'] = '#fff5ee';
+$GLOBALS['csstidy']['replace_colors']['sienna'] = '#a0522d';
+$GLOBALS['csstidy']['replace_colors']['skyblue'] = '#87ceeb';
+$GLOBALS['csstidy']['replace_colors']['slateblue'] = '#6a5acd';
+$GLOBALS['csstidy']['replace_colors']['slategray'] = '#708090';
+$GLOBALS['csstidy']['replace_colors']['snow'] = '#fffafa';
+$GLOBALS['csstidy']['replace_colors']['springgreen'] = '#00ff7f';
+$GLOBALS['csstidy']['replace_colors']['steelblue'] = '#4682b4';
+$GLOBALS['csstidy']['replace_colors']['tan'] = '#d2b48c';
+$GLOBALS['csstidy']['replace_colors']['thistle'] = '#d8bfd8';
+$GLOBALS['csstidy']['replace_colors']['tomato'] = '#ff6347';
+$GLOBALS['csstidy']['replace_colors']['turquoise'] = '#40e0d0';
+$GLOBALS['csstidy']['replace_colors']['violet'] = '#ee82ee';
+$GLOBALS['csstidy']['replace_colors']['violetred'] = '#d02090';
+$GLOBALS['csstidy']['replace_colors']['wheat'] = '#f5deb3';
+$GLOBALS['csstidy']['replace_colors']['whitesmoke'] = '#f5f5f5';
+$GLOBALS['csstidy']['replace_colors']['yellowgreen'] = '#9acd32';
+
+/**
+ * A list of all shorthand properties that are divided into four properties and/or have four subvalues
+ *
+ * @global array $GLOBALS['csstidy']['shorthands']
+ * @todo Are there new ones in CSS3?
+ * @see dissolve_4value_shorthands()
+ * @see merge_4value_shorthands()
+ * @version 1.0
+ */
+$GLOBALS['csstidy']['shorthands'] = array();
+$GLOBALS['csstidy']['shorthands']['border-color'] = array('border-top-color','border-right-color','border-bottom-color','border-left-color');
+$GLOBALS['csstidy']['shorthands']['border-style'] = array('border-top-style','border-right-style','border-bottom-style','border-left-style');
+$GLOBALS['csstidy']['shorthands']['border-width'] = array('border-top-width','border-right-width','border-bottom-width','border-left-width');
+$GLOBALS['csstidy']['shorthands']['margin'] = array('margin-top','margin-right','margin-bottom','margin-left');
+$GLOBALS['csstidy']['shorthands']['padding'] = array('padding-top','padding-right','padding-bottom','padding-left');
+$GLOBALS['csstidy']['shorthands']['-moz-border-radius'] = 0;
+
+/**
+ * All CSS Properties. Needed for csstidy::property_is_next()
+ *
+ * @global array $GLOBALS['csstidy']['all_properties']
+ * @todo Add CSS3 properties
+ * @version 1.0
+ * @see csstidy::property_is_next()
+ */
+$GLOBALS['csstidy']['all_properties']['align-content'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['align-items'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['align-self'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['alignment-adjust'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['alignment-baseline'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-delay'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-direction'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-duration'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-fill-mode'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-iteration-count'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-name'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-play-state'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['animation-timing-function'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['appearance'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['backface-visibility'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-clip'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-origin'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['background-size'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['baseline-shift'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['binding'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bleed'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bookmark-label'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bookmark-level'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bookmark-state'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bookmark-target'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom-left-radius'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom-right-radius'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image-outset'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image-repeat'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image-slice'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image-source'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-image-width'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-radius'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top-left-radius'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top-right-radius'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['box-decoration-break'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['box-shadow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['box-sizing'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['break-after'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['break-before'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['break-inside'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['clip'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['color-profile'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-count'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-fill'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-rule'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-rule-color'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-rule-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-rule-width'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-span'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['column-width'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['crop'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['dominant-baseline'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-after-adjust'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-after-align'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-before-adjust'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-before-align'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-size'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['drop-initial-value'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['fill'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['fit'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['fit-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-align'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-basis'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-direction'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-flow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-grow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-line-pack'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-order'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-pack'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-shrink'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['flex-wrap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['float-offset'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-area'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-flow'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-auto-rows'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-end'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-column-start'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-rows'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-end'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-gap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-row-start'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-areas'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-columns'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['grid-template-rows'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hanging-punctuation'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphenate-after'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphenate-before'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphenate-character'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphenate-lines'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphenate-resource'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['hyphens'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['icon'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['image-orientation'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['image-rendering'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['image-resolution'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['inline-box-align'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['justify-content'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['justify-items'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['justify-self'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['left'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-break'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-stacking'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-stacking-ruby'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-stacking-shift'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['line-stacking-strategy'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marker-offset'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marks'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marquee-direction'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marquee-loop'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marquee-play-count'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marquee-speed'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['marquee-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['move-to'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['nav-down'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['nav-index'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['nav-left'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['nav-right'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['nav-up'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['opacity'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['order'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['outline-offset'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['overflow'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['overflow-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['overflow-wrap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['overflow-x'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['overflow-y'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['page'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['page-break-after'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['page-break-before'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['page-policy'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['perspective'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['perspective-origin'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['phonemes'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['position'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['presentation-level'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['punctuation-trim'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rendering-intent'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['resize'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rest'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rest-after'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rest-before'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rotation'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['rotation-point'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['ruby-align'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['ruby-overhang'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['ruby-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['ruby-span'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['size'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['src'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['string-set'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['stroke'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['tab-size'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['target'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['target-name'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['target-new'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['target-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-align-last'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-decoration-color'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-decoration-line'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-decoration-skip'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-decoration-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-emphasis'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-emphasis-color'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-emphasis-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-emphasis-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-height'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-justify'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-outline'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-shadow'] = 'CSS2.0,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-space-collapse'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-underline-position'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['text-wrap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['top'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transform'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transform-origin'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transform-style'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transition'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transition-delay'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transition-duration'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transition-property'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['transition-timing-function'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['visibility'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-balance'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-duration'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-pitch'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-pitch-range'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-rate'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-stress'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['voice-volume'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['word-break'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1,CSS3.0';
+$GLOBALS['csstidy']['all_properties']['word-wrap'] = 'CSS3.0';
+$GLOBALS['csstidy']['all_properties']['z-index'] = 'CSS2.0,CSS2.1,CSS3.0';
+
+/**
+ * An array containing all properties that can accept a quoted string as a value.
+ *
+ * @global array $GLOBALS['csstidy']['quoted_string_properties']
+ */
+$GLOBALS['csstidy']['quoted_string_properties'] = array('content', 'font', 'font-family', 'quotes');
+
+/**
+ * An array containing all properties that can be defined multiple times without being overwritten.
+ * All unit values are included so that units like rem can be supported with fallbacks to px or em.
+ *
+ * @global array $GLOBALS['csstidy']['quoted_string_properties']
+ */
+$GLOBALS['csstidy']['multiple_properties'] = array_merge( $GLOBALS['csstidy']['color_values'], $GLOBALS['csstidy']['unit_values'], array( 'transition', 'background-image', 'border-image', 'list-style-image' ) );
+
+/**
+ * An array containing all predefined templates.
+ *
+ * @global array $GLOBALS['csstidy']['predefined_templates']
+ * @version 1.0
+ * @see csstidy::load_template()
+ */
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="at">'; //string before @rule
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>'."\n"; //bracket after @-rule
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="selector">'; //string before selector
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>'."\n"; //bracket after selector
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="property">'; //string before property
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="value">'; //string after property+before value
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="format">;</span>'."\n"; //string after value
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="format">}</span>'; //closing bracket - selector
+$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n\n"; //space between blocks {...}
+$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n".'<span class="format">}</span>'. "\n\n"; //closing bracket @-rule
+$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //indent in @-rule
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="comment">'; // before comment
+$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span>'."\n"; // after comment
+$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n"; // after last line @-rule
+
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="at">';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span> <span class="format">{</span>'."\n";
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="selector">';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">{</span>';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="property">';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="value">';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">;</span>';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="format">}</span>';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n";
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n". '<span class="format">}'."\n".'</span>';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '';
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="comment">'; // before comment
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span>'; // after comment
+$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n";
+
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="at">';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="selector">';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="property">';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="value">';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">;</span>';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="comment">'; // before comment
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span>'; // after comment
+$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
+
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="at">';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span> <span class="format">{</span>'."\n";
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="selector">';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>'."\n".'<span class="format">{</span>'."\n";
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' <span class="property">';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="value">';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="format">;</span>'."\n";
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="format">}</span>';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n\n";
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n".'<span class="format">}</span>'."\n\n";
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' ';
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="comment">'; // before comment
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>'."\n"; // after comment
+$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n";
+
+require dirname( __FILE__ ) . '/data-wp.inc.php';
diff --git a/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php b/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php
new file mode 100644
index 00000000..d4c61114
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/lang.inc.php
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * Localization of CSS Optimiser Interface of CSSTidy
+ *
+ * Copyright 2005, 2006, 2007 Florian Schmitz
+ *
+ * This file is part of CSSTidy.
+ *
+ * CSSTidy is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * CSSTidy is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
+ * @package csstidy
+ * @author Florian Schmitz (floele at gmail dot com) 2005-2007
+ * @author Brett Zamir (brettz9 at yahoo dot com) 2007
+ */
+
+if ( isset( $_GET['lang'] ) ) {
+ $l = $_GET['lang'];
+} elseif ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
+ $l = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+ $l = strtolower( substr( $l, 0, 2 ) );
+} else {
+ $l = '';
+}
+
+$l = ( in_array( $l, array( 'de', 'fr', 'zh' ) ) ) ? $l : 'en';
+
+// note 5 in all but French, and 40 in all are orphaned
+
+$lang = array();
+$lang['en'][0] = 'CSS Formatter and Optimiser/Optimizer (based on CSSTidy ';
+$lang['en'][1] = 'CSS Formatter and Optimiser';
+$lang['en'][2] = '(based on';
+$lang['en'][3] = '(plaintext)';
+$lang['en'][4] = 'Important Note:';
+$lang['en'][6] = 'Your code should be well-formed. This is <strong>not a validator</strong> which points out errors in your CSS code. To make sure that your code is valid, use the <a href="http://jigsaw.w3.org/css-validator/">W3C Validator</a>.';
+$lang['en'][7] = 'all comments are removed';
+$lang['en'][8] = 'CSS Input:';
+$lang['en'][9] = 'CSS-Code:';
+$lang['en'][10] = 'CSS from URL:';
+$lang['en'][11] = 'Code Layout:';
+$lang['en'][12] = 'Compression&#160;(code&#160;layout):';
+$lang['en'][13] = 'Highest (no readability, smallest size)';
+$lang['en'][14] = 'High (moderate readability, smaller size)';
+$lang['en'][15] = 'Standard (balance between readability and size)';
+$lang['en'][16] = 'Low (higher readability)';
+$lang['en'][17] = 'Custom (enter below)';
+$lang['en'][18] = 'Custom <a href="http://csstidy.sourceforge.net/templates.php">template</a>';
+$lang['en'][19] = 'Options';
+$lang['en'][20] = 'Sort Selectors (caution)';
+$lang['en'][21] = 'Sort Properties';
+$lang['en'][22] = 'Regroup selectors';
+$lang['en'][23] = 'Optimise shorthands';
+$lang['en'][24] = 'Compress colors';
+$lang['en'][25] = 'Lowercase selectors';
+$lang['en'][26] = 'Case for properties:';
+$lang['en'][27] = 'Lowercase';
+$lang['en'][28] = 'No or invalid CSS input or wrong URL!';
+$lang['en'][29] = 'Uppercase';
+$lang['en'][30] = 'lowercase elementnames needed for XHTML';
+$lang['en'][31] = 'Remove unnecessary backslashes';
+$lang['en'][32] = 'convert !important-hack';
+$lang['en'][33] = 'Output as file';
+$lang['en'][34] = 'Bigger compression because of smaller newlines (copy &#38; paste doesn\'t work)';
+$lang['en'][35] = 'Process CSS';
+$lang['en'][36] = 'Compression Ratio';
+$lang['en'][37] = 'Input';
+$lang['en'][38] = 'Output';
+$lang['en'][39] = 'Language';
+$lang['en'][41] = 'Attention: This may change the behavior of your CSS Code!';
+$lang['en'][42] = 'Remove last ;';
+$lang['en'][43] = 'Discard invalid properties';
+$lang['en'][44] = 'Only safe optimisations';
+$lang['en'][45] = 'Compress font-weight';
+$lang['en'][46] = 'Save comments';
+$lang['en'][47] = 'Do not change anything';
+$lang['en'][48] = 'Only separate selectors (split at ,)';
+$lang['en'][49] = 'Merge selectors with the same properties (fast)';
+$lang['en'][50] = 'Merge selectors intelligently (slow)';
+$lang['en'][51] = 'Preserve CSS';
+$lang['en'][52] = 'Save comments, hacks, etc. Most optimisations can *not* be applied if this is enabled.';
+$lang['en'][53] = 'None';
+$lang['en'][54] = 'Don\'t optimise';
+$lang['en'][55] = 'Safe optimisations';
+$lang['en'][56] = 'All optimisations';
+$lang['en'][57] = 'Add timestamp';
+$lang['en'][58] = 'Copy to clipboard';
+$lang['en'][59] = 'Back to top';
+$lang['en'][60] = 'Your browser doesn\'t support copy to clipboard.';
+$lang['en'][61] = 'For bugs and suggestions feel free to';
+$lang['en'][62] = 'contact me';
+$lang['en'][63] = 'Output CSS code as complete HTML document';
+$lang['en'][64] = 'Code';
+$lang['en'][65] = 'CSS to style CSS output';
+$lang['en'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.';
+
+
+$lang['de'][0] = 'CSS Formatierer und Optimierer (basierend auf CSSTidy ';
+$lang['de'][1] = 'CSS Formatierer und Optimierer';
+$lang['de'][2] = '(basierend auf';
+$lang['de'][3] = '(Textversion)';
+$lang['de'][4] = 'Wichtiger Hinweis:';
+$lang['de'][6] = 'Der CSS Code sollte wohlgeformt sein. Der CSS Code wird <strong>nicht auf Gültigkeit überprüft</strong>. Um sicherzugehen dass dein Code valide ist, benutze den <a href="http://jigsaw.w3.org/css-validator/">W3C Validierungsservice</a>.';
+$lang['de'][7] = 'alle Kommentare werden entfernt';
+$lang['de'][8] = 'CSS Eingabe:';
+$lang['de'][9] = 'CSS-Code:';
+$lang['de'][10] = 'CSS von URL:';
+$lang['de'][11] = 'Code Layout:';
+$lang['de'][12] = 'Komprimierung&#160;(Code&#160;Layout):';
+$lang['de'][13] = 'Höchste (keine Lesbarkeit, niedrigste Größe)';
+$lang['de'][14] = 'Hoch (mittelmäßige Lesbarkeit, geringe Größe)';
+$lang['de'][15] = 'Standard (Kompromiss zwischen Lesbarkeit und Größe)';
+$lang['de'][16] = 'Niedrig (höhere Lesbarkeit)';
+$lang['de'][17] = 'Benutzerdefiniert (unten eingeben)';
+$lang['de'][18] = 'Benutzerdefinierte <a href="http://csstidy.sourceforge.net/templates.php">Vorlage</a>';
+$lang['de'][19] = 'Optionen';
+$lang['de'][20] = 'Selektoren sortieren (Vorsicht)';
+$lang['de'][21] = 'Eigenschaften sortieren';
+$lang['de'][22] = 'Selektoren umgruppieren';
+$lang['de'][23] = 'Shorthands optimieren';
+$lang['de'][24] = 'Farben komprimieren';
+$lang['de'][25] = 'Selektoren in Kleinbuchstaben';
+$lang['de'][26] = 'Groß-/Kleinschreibung für Eigenschaften';
+$lang['de'][27] = 'Kleinbuchstaben';
+$lang['de'][28] = 'Keine oder ungültige CSS Eingabe oder falsche URL!';
+$lang['de'][29] = 'Großbuchstaben';
+$lang['de'][30] = 'kleingeschriebene Elementnamen benötigt für XHTML';
+$lang['de'][31] = 'Unnötige Backslashes entfernen';
+$lang['de'][32] = '!important-Hack konvertieren';
+$lang['de'][33] = 'Als Datei ausgeben';
+$lang['de'][34] = 'Größere Komprimierung augrund von kleineren Neuezeile-Zeichen';
+$lang['de'][35] = 'CSS verarbeiten';
+$lang['de'][36] = 'Komprimierungsrate';
+$lang['de'][37] = 'Eingabe';
+$lang['de'][38] = 'Ausgabe';
+$lang['de'][39] = 'Sprache';
+$lang['de'][41] = 'Achtung: Dies könnte das Verhalten ihres CSS-Codes verändern!';
+$lang['de'][42] = 'Letztes ; entfernen';
+$lang['de'][43] = 'Ungültige Eigenschaften entfernen';
+$lang['de'][44] = 'Nur sichere Optimierungen';
+$lang['de'][45] = 'font-weight komprimieren';
+$lang['de'][46] = 'Kommentare beibehalten';
+$lang['de'][47] = 'Nichts ändern';
+$lang['de'][48] = 'Selektoren nur trennen (am Komma)';
+$lang['de'][49] = 'Selektoren mit gleichen Eigenschaften zusammenfassen (schnell)';
+$lang['de'][50] = 'Selektoren intelligent zusammenfassen (langsam!)';
+$lang['de'][51] = 'CSS erhalten';
+$lang['de'][52] = 'Kommentare, Hacks, etc. speichern. Viele Optimierungen sind dann aber nicht mehr möglich.';
+$lang['de'][53] = 'Keine';
+$lang['de'][54] = 'Nicht optimieren';
+$lang['de'][55] = 'Sichere Optimierungen';
+$lang['de'][56] = 'Alle Optimierungen';
+$lang['de'][57] = 'Zeitstempel hinzufügen';
+$lang['de'][58] = 'Copy to clipboard';
+$lang['de'][59] = 'Back to top';
+$lang['de'][60] = 'Your browser doesn\'t support copy to clipboard.';
+$lang['de'][61] = 'For bugs and suggestions feel free to';
+$lang['de'][62] = 'contact me';
+$lang['de'][63] = 'Output CSS code as complete HTML document';
+$lang['de'][64] = 'Code';
+$lang['de'][65] = 'CSS to style CSS output';
+$lang['de'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.';
+
+
+$lang['fr'][0] = 'CSS Formatteur et Optimiseur (basé sur CSSTidy ';
+$lang['fr'][1] = 'CSS Formatteur et Optimiseur';
+$lang['fr'][2] = '(basé sur ';
+$lang['fr'][3] = '(Version texte)';
+$lang['fr'][4] = 'Note Importante&#160;:';
+$lang['fr'][6] = 'Votre code doit être valide. Ce n’est <strong>pas un validateur</strong> qui signale les erreurs dans votre code CSS. Pour être sûr que votre code est correct, utilisez le validateur&#160;: <a href="http://jigsaw.w3.org/css-validator/">W3C Validator</a>.';
+$lang['fr'][7] = 'tous les commentaires sont enlevés';
+$lang['fr'][8] = 'Champ CSS&#160;:';
+$lang['fr'][9] = 'Code CSS&#160;:';
+$lang['fr'][10] = 'CSS en provenance d’une URL&#160;:<br />';
+$lang['fr'][11] = 'Mise en page du code&#160;:';
+$lang['fr'][12] = 'Compression (mise en page du code)&#160;:';
+$lang['fr'][13] = 'La plus élevée (aucune lisibilité, taille minimale)';
+$lang['fr'][14] = 'Élevée (lisibilité modérée, petite taille)';
+$lang['fr'][15] = 'Normale (équilibre entre lisibilité et taille)';
+$lang['fr'][16] = 'Faible (lisibilité élevée)';
+$lang['fr'][17] = 'Sur mesure (entrer ci-dessous)';
+$lang['fr'][18] = '<a href="http://csstidy.sourceforge.net/templates.php">Gabarit</a> sur mesure';
+$lang['fr'][19] = 'Options';
+$lang['fr'][20] = 'Trier les sélecteurs (attention)';
+$lang['fr'][21] = 'Trier les propriétés';
+$lang['fr'][22] = 'Regrouper les sélecteurs';
+$lang['fr'][23] = 'Propriétés raccourcies';
+$lang['fr'][24] = 'Compresser les couleurs';
+$lang['fr'][25] = 'Sélecteurs en minuscules';
+$lang['fr'][26] = 'Case pour les propriétés&#160;:';
+$lang['fr'][27] = 'Minuscule';
+$lang['fr'][28] = 'CSS non valide ou URL incorrecte&#160;!';
+$lang['fr'][29] = 'Majuscule';
+$lang['fr'][30] = 'les noms des éléments en minuscules (indispensables pour XHTML)';
+$lang['fr'][31] = 'enlever les antislashs inutiles';
+$lang['fr'][32] = 'convertir !important-hack';
+$lang['fr'][33] = 'Sauver en tant que fichier';
+$lang['fr'][34] = 'Meilleure compression grâce aux caractères de saut de ligne plus petits (copier &#38; coller ne marche pas)';
+$lang['fr'][35] = 'Compresser la CSS';
+$lang['fr'][36] = 'Facteur de Compression';
+$lang['fr'][37] = 'Entrée';
+$lang['fr'][38] = 'Sortie';
+$lang['fr'][39] = 'Langue';
+$lang['fr'][41] = 'Attention&#160;: ceci peut changer le comportement de votre code CSS&#160;!';
+$lang['fr'][42] = 'Enlever le dernier ;';
+$lang['fr'][43] = 'Supprimer les propriétés non valide';
+$lang['fr'][44] = 'Seulement les optimisations sûres';
+$lang['fr'][45] = 'Compresser font-weight';
+$lang['fr'][46] = 'Sauvegarder les commentaires ';
+$lang['fr'][47] = 'Ne rien changer';
+$lang['fr'][48] = 'Sépare les sélecteurs (sépare au niveau de ,)';
+$lang['fr'][49] = 'Fusionne les sélecteurs avec les mêmes propriétés (rapide)';
+$lang['fr'][50] = 'Fusionne les sélecteurs intelligemment (lent)';
+$lang['fr'][51] = 'Préserver la CSS';
+$lang['fr'][52] = 'Sauvegarder les commentaires, hacks, etc. La plupart des optimisations ne peuvent *pas* être appliquées si cela est activé.';
+$lang['fr'][53] = 'Aucun';
+$lang['fr'][54] = 'Ne pas optimiser';
+$lang['fr'][55] = 'Optimisations sûres';
+$lang['fr'][56] = 'Toutes les optimisations';
+$lang['fr'][57] = 'Ajouter un timestamp';
+$lang['fr'][58] = 'Copier dans le presse-papiers';
+$lang['fr'][59] = 'Retour en haut';
+$lang['fr'][60] = 'Votre navigateur ne suporte pas la copie vers le presse-papiers.';
+$lang['fr'][61] = 'Pour signaler des bugs ou pour des suggestions,';
+$lang['fr'][62] = 'contactez-moi';
+$lang['fr'][63] = 'Sauver le code CSS comme document complet HTML';
+$lang['fr'][64] = 'Code';
+$lang['fr'][65] = 'CSS pour colorier la sortie CSS';
+$lang['fr'][66] = 'Vous devez aller dans about:config dans votre barre d’adresse, selectionner \'signed.applets.codebase_principal_support\' dans le champ Filtre et attribuez-lui la valeur \'true\' pour utiliser cette fonctionnalité; toutefois, soyez conscient que cela augmente les risques de sécurité.';
+
+
+$lang['zh'][0] = 'CSS整形與最佳化工具(使用 CSSTidy ';
+$lang['zh'][1] = 'CSS整形與最佳化工具';
+$lang['zh'][2] = '(使用';
+$lang['zh'][3] = '(純文字)';
+$lang['zh'][4] = '重要事項:';
+$lang['zh'][6] = '你的原始碼必須是良構的(well-formed). 這個工具<strong>沒有內建驗證器(validator)</strong>. 驗證器能夠指出你CSS原始碼裡的錯誤. 請使用 <a href="http://jigsaw.w3.org/css-validator/">W3C 驗證器</a>, 確保你的原始碼合乎規範.';
+$lang['zh'][7] = '所有註解都移除了';
+$lang['zh'][8] = 'CSS 輸入:';
+$lang['zh'][9] = 'CSS 原始碼:';
+$lang['zh'][10] = 'CSS 檔案網址(URL):';
+$lang['zh'][11] = '原始碼規劃:';
+$lang['zh'][12] = '壓縮程度(原始碼規劃):';
+$lang['zh'][13] = '最高 (沒有辦法讀, 檔案最小)';
+$lang['zh'][14] = '高 (適度的可讀性, 檔案小)';
+$lang['zh'][15] = '標準 (兼顧可讀性與檔案大小)';
+$lang['zh'][16] = '低 (注重可讀性)';
+$lang['zh'][17] = '自訂 (在下方設定)';
+$lang['zh'][18] = '自訂<a href="http://csstidy.sourceforge.net/templates.php">樣板</a>';
+$lang['zh'][19] = '選項';
+$lang['zh'][20] = '整理選擇符(請謹慎使用)';
+$lang['zh'][21] = '整理屬性';
+$lang['zh'][22] = '重組選擇符';
+$lang['zh'][23] = '速記法(shorthand)最佳化';
+$lang['zh'][24] = '壓縮色彩語法';
+$lang['zh'][25] = '改用小寫選擇符';
+$lang['zh'][26] = '屬性的字形:';
+$lang['zh'][27] = '小寫';
+$lang['zh'][28] = '沒有輸入CSS, 語法不符合規定, 或是網址錯誤!';
+$lang['zh'][29] = '大寫';
+$lang['zh'][30] = 'XHTML必須使用小寫的元素名稱';
+$lang['zh'][31] = '移除不必要的反斜線';
+$lang['zh'][32] = '轉換 !important-hack';
+$lang['zh'][33] = '輸出成檔案形式';
+$lang['zh'][34] = '由於比較少換行字元, 會有更大的壓縮比率(複製&#38;貼上沒有用)';
+$lang['zh'][35] = '執行';
+$lang['zh'][36] = '壓縮比率';
+$lang['zh'][37] = '輸入';
+$lang['zh'][38] = '輸出';
+$lang['zh'][39] = '語言';
+$lang['zh'][41] = '注意: 這或許會變更你CSS原始碼的行為!';
+$lang['zh'][42] = '除去最後一個分號';
+$lang['zh'][43] = '拋棄不符合規定的屬性';
+$lang['zh'][44] = '只安全地最佳化';
+$lang['zh'][45] = '壓縮 font-weight';
+$lang['zh'][46] = '保留註解';
+$lang['zh'][47] = '什麼都不要改';
+$lang['zh'][48] = '只分開原本用逗號分隔的選擇符';
+$lang['zh'][49] = '合併有相同屬性的選擇符(快速)';
+$lang['zh'][50] = '聰明地合併選擇符(慢速)';
+$lang['zh'][51] = '保護CSS';
+$lang['zh'][52] = '保留註解與 hack 等等. 如果啟用這個選項, 大多數的最佳化程序都不會執行.';
+$lang['zh'][53] = '不改變';
+$lang['zh'][54] = '不做最佳化';
+$lang['zh'][55] = '安全地最佳化';
+$lang['zh'][56] = '全部最佳化';
+$lang['zh'][57] = '加上時間戳記';
+$lang['zh'][58] = '复制到剪贴板';
+$lang['zh'][59] = '回到页面上方';
+$lang['zh'][60] = '你的浏览器不支持复制到剪贴板。';
+$lang['zh'][61] = '如果程序有错误或你有建议,欢迎';
+$lang['zh'][62] = '和我联系';
+$lang['zh'][63] = 'Output CSS code as complete HTML document';
+$lang['zh'][64] = '代码';
+$lang['zh'][65] = 'CSS to style CSS output';
+$lang['zh'][66] = 'You need to go to about:config in your URL bar, select \'signed.applets.codebase_principal_support\' in the filter field, and set its value to true in order to use this feature; however, be aware that doing so increases security risks.';
diff --git a/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl b/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl
new file mode 100644
index 00000000..9499e839
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/csstidy/wordpress-standard.tpl
@@ -0,0 +1,10 @@
+| {
+|| {
+| | |;
+|}|
+
+|
+}
+
+| ||
+|
diff --git a/plugins/jetpack/modules/custom-css/custom-css-4.7.php b/plugins/jetpack/modules/custom-css/custom-css-4.7.php
new file mode 100644
index 00000000..bb72caec
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css-4.7.php
@@ -0,0 +1,1165 @@
+<?php
+/**
+ * Alternate Custom CSS source for 4.7 compat.
+ *
+ * @since 4.4.2
+ *
+ * @package Jetpack
+ */
+
+/**
+ * Class Jetpack_Custom_CSS_Enhancements
+ */
+class Jetpack_Custom_CSS_Enhancements {
+ /**
+ * Set up the actions and filters needed for our compatability layer on top of core's Custom CSS implementation.
+ */
+ public static function add_hooks() {
+ add_action( 'init', array( __CLASS__, 'init' ) );
+ add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
+ add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'customize_controls_enqueue_scripts' ) );
+ add_action( 'customize_register', array( __CLASS__, 'customize_register' ) );
+ add_filter( 'map_meta_cap', array( __CLASS__, 'map_meta_cap' ), 20, 2 );
+ add_action( 'customize_preview_init', array( __CLASS__, 'customize_preview_init' ) );
+ add_filter( '_wp_post_revision_fields', array( __CLASS__, '_wp_post_revision_fields' ), 10, 2 );
+ add_action( 'load-revision.php', array( __CLASS__, 'load_revision_php' ) );
+
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'wp_enqueue_scripts' ) );
+
+ // Handle Sass/LESS.
+ add_filter( 'customize_value_custom_css', array( __CLASS__, 'customize_value_custom_css' ), 10, 2 );
+ add_filter( 'customize_update_custom_css_post_content_args', array( __CLASS__, 'customize_update_custom_css_post_content_args' ), 10, 3 );
+ add_filter( 'update_custom_css_data', array( __CLASS__, 'update_custom_css_data' ), 10, 2 );
+
+ // Stuff for stripping out the theme's default stylesheet...
+ add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) );
+ add_filter( 'safecss_skip_stylesheet', array( __CLASS__, 'preview_skip_stylesheet' ) );
+
+ // Stuff for overriding content width...
+ add_action( 'customize_preview_init', array( __CLASS__, 'preview_content_width' ) );
+ add_filter( 'jetpack_content_width', array( __CLASS__, 'jetpack_content_width' ) );
+ add_filter( 'editor_max_image_size', array( __CLASS__, 'editor_max_image_size' ), 10, 3 );
+ add_action( 'template_redirect', array( __CLASS__, 'set_content_width' ) );
+ add_action( 'admin_init', array( __CLASS__, 'set_content_width' ) );
+
+ // Stuff?
+ }
+
+ /**
+ * Things that we do on init.
+ */
+ public static function init() {
+ $min = '.min';
+ if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
+ $min = '';
+ }
+
+ wp_register_style( 'jetpack-codemirror', plugins_url( 'custom-css/css/codemirror.css', __FILE__ ), array(), '20120905' );
+ wp_register_style( 'jetpack-customizer-css', plugins_url( 'custom-css/css/customizer-control.css', __FILE__ ), array(), '20140728' );
+ wp_register_script( 'jetpack-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
+ $src = Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/core-customizer-css.core-4.9.min.js',
+ 'modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js'
+ );
+ wp_register_script( 'jetpack-customizer-css', $src, array( 'customize-controls', 'underscore' ), JETPACK__VERSION, true );
+
+ wp_register_script(
+ 'jetpack-customizer-css-preview',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/core-customizer-css-preview.min.js',
+ 'modules/custom-css/custom-css/js/core-customizer-css-preview.js'
+ ),
+ array( 'customize-selective-refresh' ),
+ JETPACK__VERSION,
+ true
+ );
+
+ remove_action( 'wp_head', 'wp_custom_css_cb', 11 ); // 4.7.0 had it at 11, 4.7.1 moved it to 101.
+ remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
+ add_action( 'wp_head', array( __CLASS__, 'wp_custom_css_cb' ), 101 );
+
+ if ( isset( $_GET['custom-css'] ) ) {
+ self::print_linked_custom_css();
+ }
+ }
+
+ /**
+ * Things that we do on init when the Customize Preview is loading.
+ */
+ public static function customize_preview_init() {
+ add_filter( 'wp_get_custom_css', array( __CLASS__, 'customize_preview_wp_get_custom_css' ) );
+ }
+
+ /**
+ * Print the current Custom CSS. This is for linking instead of printing directly.
+ */
+ public static function print_linked_custom_css() {
+ header( 'Content-type: text/css' );
+ header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + YEAR_IN_SECONDS ) . ' GMT' );
+ echo wp_get_custom_css();
+ exit;
+ }
+
+ /**
+ * Re-map the Edit CSS capability.
+ *
+ * Core, by default, restricts this to users that have `unfiltered_html` which
+ * would make the feature unusable in multi-site by non-super-admins, due to Core
+ * not shipping any solid sanitization.
+ *
+ * We're expanding who can use it, and then conditionally applying CSSTidy
+ * sanitization to users that do not have the `unfiltered_html` capability.
+ *
+ * @param array $caps Returns the user's actual capabilities.
+ * @param string $cap Capability name.
+ *
+ * @return array $caps
+ */
+ public static function map_meta_cap( $caps, $cap ) {
+ if ( 'edit_css' === $cap ) {
+ $caps = array( 'edit_theme_options' );
+ }
+ return $caps;
+ }
+
+ /**
+ * Handle our admin menu item and legacy page declaration.
+ */
+ public static function admin_menu() {
+ // Add in our legacy page to support old bookmarks and such.
+ add_submenu_page( null, __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss', array( __CLASS__, 'admin_page' ) );
+
+ // Add in our new page slug that will redirect to the customizer.
+ $hook = add_theme_page( __( 'CSS', 'jetpack' ), __( 'Edit CSS', 'jetpack' ), 'edit_theme_options', 'editcss-customizer-redirect', array( __CLASS__, 'admin_page' ) );
+ add_action( "load-{$hook}", array( __CLASS__, 'customizer_redirect' ) );
+ }
+
+ /**
+ * Handle the redirect for the customizer. This is necessary because
+ * we can't directly add customizer links to the admin menu.
+ *
+ * There is a core patch in trac that would make this unnecessary.
+ *
+ * @link https://core.trac.wordpress.org/ticket/39050
+ */
+ public static function customizer_redirect() {
+ wp_safe_redirect( self::customizer_link( array(
+ 'return_url' => wp_get_referer(),
+ ) ) );
+ exit;
+ }
+
+ /**
+ * Shows Preprocessor code in the Revisions screen, and ensures that post_content_filtered
+ * is maintained on revisions
+ *
+ * @param array $fields Post fields pertinent to revisions.
+ * @param array $post A post array being processed for insertion as a post revision.
+ *
+ * @return array $fields Modified array to include post_content_filtered.
+ */
+ public static function _wp_post_revision_fields( $fields, $post ) {
+ // None of the fields in $post are required to be passed in this filter.
+ if ( ! isset( $post['post_type'], $post['ID'] ) ) {
+ return $fields;
+ }
+
+ // If we're passed in a revision, go get the main post instead.
+ if ( 'revision' === $post['post_type'] ) {
+ $main_post_id = wp_is_post_revision( $post['ID'] );
+ $post = get_post( $main_post_id, ARRAY_A );
+ }
+ if ( 'custom_css' === $post['post_type'] ) {
+ $fields['post_content'] = __( 'CSS', 'jetpack' );
+ $fields['post_content_filtered'] = __( 'Preprocessor', 'jetpack' );
+ }
+ return $fields;
+ }
+
+ /**
+ * Get the published custom CSS post.
+ *
+ * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
+ * @return WP_Post|null
+ */
+ public static function get_css_post( $stylesheet = '' ) {
+ return wp_get_custom_css_post( $stylesheet );
+ }
+
+ /**
+ * Override Core's `wp_custom_css_cb` method to provide linking to custom css.
+ */
+ public static function wp_custom_css_cb() {
+ $styles = wp_get_custom_css();
+ if ( strlen( $styles ) > 2000 && ! is_customize_preview() ) :
+ // Add a cache buster to the url.
+ $url = home_url( '/' );
+ $url = add_query_arg( 'custom-css', substr( md5( $styles ), -10 ), $url );
+ ?>
+ <link rel="stylesheet" type="text/css" id="wp-custom-css" href="<?php echo esc_url( $url ); ?>" />
+ <?php elseif ( $styles || is_customize_preview() ) : ?>
+ <style type="text/css" id="wp-custom-css">
+ <?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
+ </style>
+ <?php endif;
+ }
+
+ /**
+ * Get the ID of a Custom CSS post tying to a given stylesheet.
+ *
+ * @param string $stylesheet Stylesheet name.
+ *
+ * @return int $post_id Post ID.
+ */
+ public static function post_id( $stylesheet = '' ) {
+ $post = self::get_css_post( $stylesheet );
+ if ( $post instanceof WP_Post ) {
+ return $post->ID;
+ }
+ return 0;
+ }
+
+ /**
+ * Partial for use in the Customizer.
+ */
+ public static function echo_custom_css_partial() {
+ echo wp_get_custom_css();
+ }
+
+ /**
+ * Admin page!
+ *
+ * This currently has two main uses -- firstly to display the css for an inactive
+ * theme if there are no revisions attached it to a legacy bug, and secondly to
+ * handle folks that have bookmarkes in their browser going to the old page for
+ * managing Custom CSS in Jetpack.
+ *
+ * If we ever add back in a non-Customizer CSS editor, this would be the place.
+ */
+ public static function admin_page() {
+ $post = null;
+ $stylesheet = null;
+ if ( isset( $_GET['id'] ) ) {
+ $post_id = absint( $_GET['id'] );
+ $post = get_post( $post_id );
+ if ( $post instanceof WP_Post && 'custom_css' === $post->post_type ) {
+ $stylesheet = $post->post_title;
+ }
+ }
+ ?>
+ <div class="wrap">
+ <?php self::revisions_switcher_box( $stylesheet ); ?>
+ <h1>
+ <?php
+ if ( $post ) {
+ printf( 'Custom CSS for &#8220;%1$s&#8221;', wp_get_theme( $stylesheet )->Name );
+ } else {
+ esc_html_e( 'Custom CSS', 'jetpack' );
+ }
+ if ( current_user_can( 'customize' ) ) {
+ printf(
+ ' <a class="page-title-action hide-if-no-customize" href="%1$s">%2$s</a>',
+ esc_url( self::customizer_link() ),
+ esc_html__( 'Manage with Live Preview', 'jetpack' )
+ );
+ }
+ ?>
+ </h1>
+ <p><?php esc_html_e( 'Custom CSS is now managed in the Customizer.', 'jetpack' ); ?></p>
+ <?php if ( $post ) : ?>
+ <div class="revisions">
+ <h3><?php esc_html_e( 'CSS', 'jetpack' ); ?></h3>
+ <textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content ); ?></textarea>
+ <?php if ( $post->post_content_filtered ) : ?>
+ <h3><?php esc_html_e( 'Preprocessor', 'jetpack' ); ?></h3>
+ <textarea class="widefat" readonly><?php echo esc_textarea( $post->post_content_filtered ); ?></textarea>
+ <?php endif; ?>
+ </div>
+ <?php endif; ?>
+ </div>
+
+ <style>
+ .other-themes-wrap {
+ float: right;
+ background-color: #fff;
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ padding: 5px 10px;
+ margin-bottom: 10px;
+ }
+ .other-themes-wrap label {
+ display: block;
+ margin-bottom: 10px;
+ }
+ .other-themes-wrap select {
+ float: left;
+ width: 77%;
+ }
+ .other-themes-wrap button {
+ float: right;
+ width: 20%;
+ }
+ .revisions {
+ clear: both;
+ }
+ .revisions textarea {
+ min-height: 300px;
+ background: #fff;
+ }
+ </style>
+ <script>
+ (function($){
+ var $switcher = $('.other-themes-wrap');
+ $switcher.find('button').on('click', function(e){
+ e.preventDefault();
+ if ( $switcher.find('select').val() ) {
+ window.location.href = $switcher.find('select').val();
+ }
+ });
+ })(jQuery);
+ </script>
+ <?php
+ }
+
+ /**
+ * Build the URL to deep link to the Customizer.
+ *
+ * You can modify the return url via $args.
+ *
+ * @param array $args Array of parameters.
+ * @return string
+ */
+ public static function customizer_link( $args = array() ) {
+ $args = wp_parse_args( $args, array(
+ 'return_url' => urlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) ),
+ ) );
+
+ return add_query_arg(
+ array(
+ array(
+ 'autofocus' => array(
+ 'section' => 'custom_css',
+ ),
+ ),
+ 'return' => $args['return_url'],
+ ),
+ admin_url( 'customize.php' )
+ );
+ }
+
+ /**
+ * Handle the enqueueing and localizing for scripts to be used in the Customizer.
+ */
+ public static function customize_controls_enqueue_scripts() {
+ wp_enqueue_style( 'jetpack-customizer-css' );
+ wp_enqueue_script( 'jetpack-customizer-css' );
+
+ $content_help = __( 'Set a different content width for full size images.', 'jetpack' );
+ if ( ! empty( $GLOBALS['content_width'] ) ) {
+ $content_help .= sprintf(
+ _n( ' The default content width for the <strong>%1$s</strong> theme is %2$d pixel.', ' The default content width for the <strong>%1$s</strong> theme is %2$d pixels.', intval( $GLOBALS['content_width'] ), 'jetpack' ),
+ wp_get_theme()->Name,
+ intval( $GLOBALS['content_width'] )
+ );
+ }
+
+ wp_localize_script( 'jetpack-customizer-css', '_jp_css_settings', array(
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ 'useRichEditor' => ! jetpack_is_mobile() && apply_filters( 'safecss_use_ace', true ),
+ 'areThereCssRevisions' => self::are_there_css_revisions(),
+ 'revisionsUrl' => self::get_revisions_url(),
+ 'cssHelpUrl' => '//en.support.wordpress.com/custom-design/editing-css/',
+ 'l10n' => array(
+ 'mode' => __( 'Start Fresh', 'jetpack' ),
+ 'mobile' => __( 'On Mobile', 'jetpack' ),
+ 'contentWidth' => $content_help,
+ 'revisions' => _x( 'See full history', 'Toolbar button to see full CSS revision history', 'jetpack' ),
+ 'css_help_title' => _x( 'Help', 'Toolbar button to get help with custom CSS', 'jetpack' ),
+ ),
+ ));
+ }
+
+ /**
+ * Check whether there are CSS Revisions for a given theme.
+ *
+ * Going forward, there should always be, but this was necessitated
+ * early on by https://core.trac.wordpress.org/ticket/30854
+ *
+ * @param string $stylesheet Stylesheet name.
+ *
+ * @return bool|null|WP_Post
+ */
+ public static function are_there_css_revisions( $stylesheet = '' ) {
+ $post = wp_get_custom_css_post( $stylesheet );
+ if ( empty( $post ) ) {
+ return $post;
+ }
+ return (bool) wp_get_post_revisions( $post );
+ }
+
+ /**
+ * Core doesn't have a function to get the revisions url for a given post ID.
+ *
+ * @param string $stylesheet Stylesheet name.
+ *
+ * @return null|string|void
+ */
+ public static function get_revisions_url( $stylesheet = '' ) {
+ $post = wp_get_custom_css_post( $stylesheet );
+
+ // If we have any currently saved customizations...
+ if ( $post instanceof WP_Post ) {
+ $revisions = wp_get_post_revisions( $post->ID, array( 'posts_per_page' => 1 ) );
+ if ( empty( $revisions ) || is_wp_error( $revisions ) ) {
+ return admin_url( 'themes.php?page=editcss' );
+ }
+ $revision = reset( $revisions );
+ return get_edit_post_link( $revision->ID );
+ }
+
+ return admin_url( 'themes.php?page=editcss' );
+ }
+
+ /**
+ * Get a map of all theme names and theme stylesheets for mapping stuff.
+ *
+ * @return array
+ */
+ public static function get_themes() {
+ $themes = wp_get_themes( array( 'errors' => null ) );
+ $all = array();
+ foreach ( $themes as $theme ) {
+ $all[ $theme->name ] = $theme->stylesheet;
+ }
+ return $all;
+ }
+
+ /**
+ * When we need to get all themes that have Custom CSS saved.
+ *
+ * @return array
+ */
+ public static function get_all_themes_with_custom_css() {
+ $themes = self::get_themes();
+ $custom_css = get_posts( array(
+ 'post_type' => 'custom_css',
+ 'post_status' => get_post_stati(),
+ 'number' => -1,
+ 'order' => 'DESC',
+ 'orderby' => 'modified',
+ ) );
+ $return = array();
+
+ foreach ( $custom_css as $post ) {
+ $stylesheet = $post->post_title;
+ $label = array_search( $stylesheet, $themes );
+
+ if ( ! $label ) {
+ continue;
+ }
+
+ $return[ $stylesheet ] = array(
+ 'label' => $label,
+ 'post' => $post,
+ );
+ }
+
+ return $return;
+ }
+
+ /**
+ * Handle the enqueueing of scripts for customize previews.
+ */
+ public static function wp_enqueue_scripts() {
+ if ( is_customize_preview() ) {
+ wp_enqueue_script( 'jetpack-customizer-css-preview' );
+ wp_localize_script( 'jetpack-customizer-css-preview', 'jpCustomizerCssPreview', array(
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ 'preprocessors' => apply_filters( 'jetpack_custom_css_preprocessors', array() ),
+ ));
+ }
+ }
+
+ /**
+ * Sanitize the CSS for users without `unfiltered_html`.
+ *
+ * @param string $css Input CSS.
+ * @param array $args Array of CSS options.
+ *
+ * @return mixed|string
+ */
+ public static function sanitize_css( $css, $args = array() ) {
+ $args = wp_parse_args( $args, array(
+ 'force' => false,
+ 'preprocessor' => null,
+ ) );
+
+ if ( $args['force'] || ! current_user_can( 'unfiltered_html' ) ) {
+
+ $warnings = array();
+
+ safecss_class();
+ $csstidy = new csstidy();
+ $csstidy->optimise = new safecss( $csstidy );
+
+ $csstidy->set_cfg( 'remove_bslash', false );
+ $csstidy->set_cfg( 'compress_colors', false );
+ $csstidy->set_cfg( 'compress_font-weight', false );
+ $csstidy->set_cfg( 'optimise_shorthands', 0 );
+ $csstidy->set_cfg( 'remove_last_;', false );
+ $csstidy->set_cfg( 'case_properties', false );
+ $csstidy->set_cfg( 'discard_invalid_properties', true );
+ $csstidy->set_cfg( 'css_level', 'CSS3.0' );
+ $csstidy->set_cfg( 'preserve_css', true );
+ $csstidy->set_cfg( 'template', dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' );
+
+ // Test for some preg_replace stuff.
+ {
+ $prev = $css;
+ $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css );
+ // prevent content: '\3434' from turning into '\\3434'.
+ $css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css );
+ if ( $css !== $prev ) {
+ $warnings[] = 'preg_replace found stuff';
+ }
+ }
+
+ // Some people put weird stuff in their CSS, KSES tends to be greedy.
+ $css = str_replace( '<=', '&lt;=', $css );
+
+ // Test for some kses stuff.
+ {
+ $prev = $css;
+ // Why KSES instead of strip_tags? Who knows?
+ $css = wp_kses_split( $css, array(), array() );
+ $css = str_replace( '&gt;', '>', $css ); // kses replaces lone '>' with &gt;
+ // Why both KSES and strip_tags? Because we just added some '>'.
+ $css = strip_tags( $css );
+
+ if ( $css != $prev ) {
+ $warnings[] = 'kses found stuff';
+ }
+ }
+
+ // if we're not using a preprocessor.
+ if ( ! $args['preprocessor'] ) {
+
+ /** This action is documented in modules/custom-css/custom-css.php */
+ do_action( 'safecss_parse_pre', $csstidy, $css, $args );
+
+ $csstidy->parse( $css );
+
+ /** This action is documented in modules/custom-css/custom-css.php */
+ do_action( 'safecss_parse_post', $csstidy, $warnings, $args );
+
+ $css = $csstidy->print->plain();
+ }
+ }
+ return $css;
+ }
+
+ /**
+ * Override $content_width in customizer previews.
+ */
+ public static function preview_content_width() {
+ global $wp_customize;
+ if ( ! is_customize_preview() ) {
+ return;
+ }
+
+ $setting = $wp_customize->get_setting( 'jetpack_custom_css[content_width]' );
+ if ( ! $setting ) {
+ return;
+ }
+
+ $customized_content_width = (int) $setting->post_value();
+ if ( ! empty( $customized_content_width ) ) {
+ $GLOBALS['content_width'] = $customized_content_width;
+ }
+ }
+
+ /**
+ * Filter the current theme's stylesheet for potentially nullifying it.
+ *
+ * @param string $current Stylesheet URI for the current theme/child theme.
+ *
+ * @return mixed|void
+ */
+ static function style_filter( $current ) {
+ if ( is_admin() ) {
+ return $current;
+ } elseif ( self::is_freetrial() && ( ! self::is_preview() || ! current_user_can( 'switch_themes' ) ) ) {
+ return $current;
+ } elseif ( self::skip_stylesheet() ) {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) );
+ }
+
+ return $current;
+ }
+
+ /**
+ * Determine whether or not we should have the theme skip its main stylesheet.
+ *
+ * @return mixed The truthiness of this value determines whether the stylesheet should be skipped.
+ */
+ static function skip_stylesheet() {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null );
+ if ( ! is_null( $skip_stylesheet ) ) {
+ return $skip_stylesheet;
+ }
+
+ $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
+ if ( isset( $jetpack_custom_css['replace'] ) ) {
+ return $jetpack_custom_css['replace'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Override $content_width in customizer previews.
+ *
+ * Runs on `safecss_skip_stylesheet` filter.
+ *
+ * @param bool $skip_value Should the stylesheet be skipped.
+ *
+ * @return null|bool
+ */
+ public static function preview_skip_stylesheet( $skip_value ) {
+ global $wp_customize;
+ if ( ! is_customize_preview() ) {
+ return $skip_value;
+ }
+
+ $setting = $wp_customize->get_setting( 'jetpack_custom_css[replace]' );
+ if ( ! $setting ) {
+ return $skip_value;
+ }
+
+ $customized_replace = $setting->post_value();
+ if ( null !== $customized_replace ) {
+ return $customized_replace;
+ }
+
+ return $skip_value;
+ }
+
+ /**
+ * Add Custom CSS section and controls.
+ *
+ * @param WP_Customize_Manager $wp_customize WP_Customize_Manager instance.
+ */
+ public static function customize_register( $wp_customize ) {
+
+ /**
+ * SETTINGS.
+ */
+
+ $wp_customize->add_setting( 'jetpack_custom_css[preprocessor]', array(
+ 'default' => '',
+ 'transport' => 'postMessage',
+ 'sanitize_callback' => array( __CLASS__, 'sanitize_preprocessor' ),
+ ) );
+
+ $wp_customize->add_setting( 'jetpack_custom_css[replace]', array(
+ 'default' => false,
+ 'transport' => 'refresh',
+ ) );
+
+ $wp_customize->add_setting( 'jetpack_custom_css[content_width]', array(
+ 'default' => '',
+ 'transport' => 'refresh',
+ 'sanitize_callback' => array( __CLASS__, 'intval_base10' ),
+ ) );
+
+ // Add custom sanitization to the core css customizer setting.
+ foreach ( $wp_customize->settings() as $setting ) {
+ if ( $setting instanceof WP_Customize_Custom_CSS_Setting ) {
+ add_filter( "customize_sanitize_{$setting->id}", array( __CLASS__, 'sanitize_css_callback' ), 10, 2 );
+ }
+ }
+
+ /**
+ * CONTROLS.
+ */
+
+ // Overwrite or Tweak the Core Control.
+ $core_custom_css = $wp_customize->get_control( 'custom_css' );
+ if ( $core_custom_css ) {
+ if ( $core_custom_css instanceof WP_Customize_Code_Editor_Control ) {
+ // In WP 4.9, we let the Core CodeMirror control keep running the show, but hook into it to tweak stuff.
+ $types = array(
+ 'default' => 'text/css',
+ 'less' => 'text/x-less',
+ 'sass' => 'text/x-scss',
+ );
+ $preprocessor = $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value();
+ if ( isset( $types[ $preprocessor ] ) ) {
+ $core_custom_css->code_type = $types[ $preprocessor ];
+ }
+ } else {
+ // Core < 4.9 Fallback
+ $core_custom_css->type = 'jetpackCss';
+ }
+ }
+
+ $wp_customize->selective_refresh->add_partial( 'custom_css', array(
+ 'type' => 'custom_css',
+ 'selector' => '#wp-custom-css',
+ 'container_inclusive' => false,
+ 'fallback_refresh' => false,
+ 'settings' => array(
+ 'custom_css[' . $wp_customize->get_stylesheet() . ']',
+ 'jetpack_custom_css[preprocessor]',
+ ),
+ 'render_callback' => array( __CLASS__, 'echo_custom_css_partial' ),
+ ) );
+
+ $wp_customize->add_control( 'wpcom_custom_css_content_width_control', array(
+ 'type' => 'text',
+ 'label' => __( 'Media Width', 'jetpack' ),
+ 'section' => 'custom_css',
+ 'settings' => 'jetpack_custom_css[content_width]',
+ ) );
+
+ $wp_customize->add_control( 'jetpack_css_mode_control', array(
+ 'type' => 'checkbox',
+ 'label' => __( 'Don\'t use the theme\'s original CSS.', 'jetpack' ),
+ 'section' => 'custom_css',
+ 'settings' => 'jetpack_custom_css[replace]',
+ ) );
+
+ /**
+ * An action to grab on to if another Jetpack Module would like to add its own controls.
+ *
+ * @module custom-css
+ *
+ * @since 4.4.2
+ *
+ * @param $wp_customize The WP_Customize object.
+ */
+ do_action( 'jetpack_custom_css_customizer_controls', $wp_customize );
+
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ if ( ! empty( $preprocessors ) ) {
+ $preprocessor_choices = array(
+ '' => __( 'None', 'jetpack' ),
+ );
+
+ foreach ( $preprocessors as $preprocessor_key => $processor ) {
+ $preprocessor_choices[ $preprocessor_key ] = $processor['name'];
+ }
+
+ $wp_customize->add_control( 'jetpack_css_preprocessors_control', array(
+ 'type' => 'select',
+ 'choices' => $preprocessor_choices,
+ 'label' => __( 'Preprocessor', 'jetpack' ),
+ 'section' => 'custom_css',
+ 'settings' => 'jetpack_custom_css[preprocessor]',
+ ) );
+ }
+
+ }
+
+ /**
+ * The callback to handle sanitizing the CSS. Takes different arguments, hence the proxy function.
+ *
+ * @param mixed $css Value of the setting.
+ * @param WP_Customize_Setting $setting WP_Customize_Setting instance.
+ *
+ * @return mixed|string
+ */
+ public static function sanitize_css_callback( $css, $setting ) {
+ global $wp_customize;
+ return self::sanitize_css( $css, array(
+ 'preprocessor' => $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value(),
+ ) );
+ }
+
+ /**
+ * Flesh out for wpcom.
+ *
+ * @todo
+ *
+ * @return bool
+ */
+ public static function is_freetrial() {
+ return false;
+ }
+
+ /**
+ * Flesh out for wpcom.
+ *
+ * @todo
+ *
+ * @return bool
+ */
+ public static function is_preview() {
+ return false;
+ }
+
+ /**
+ * Output the custom css for customize preview.
+ *
+ * @param string $css Custom CSS content.
+ *
+ * @return mixed
+ */
+ public static function customize_preview_wp_get_custom_css( $css ) {
+ global $wp_customize;
+
+ $preprocessor = $wp_customize->get_setting( 'jetpack_custom_css[preprocessor]' )->value();
+
+ // If it's empty, just return.
+ if ( empty( $preprocessor ) ) {
+ return $css;
+ }
+
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ if ( isset( $preprocessors[ $preprocessor ] ) ) {
+ return call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
+ }
+
+ return $css;
+ }
+
+ /**
+ * Add CSS preprocessing to our CSS if it is supported.
+ *
+ * @param mixed $css Value of the setting.
+ * @param WP_Customize_Setting $setting WP_Customize_Setting instance.
+ *
+ * @return string
+ */
+ public static function customize_value_custom_css( $css, $setting ) {
+ // Find the current preprocessor.
+ $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
+ if ( isset( $jetpack_custom_css['preprocessor'] ) ) {
+ $preprocessor = $jetpack_custom_css['preprocessor'];
+ }
+
+ // If it's not supported, just return.
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ if ( ! isset( $preprocessors[ $preprocessor ] ) ) {
+ return $css;
+ }
+
+ // Swap it for the `post_content_filtered` instead.
+ $post = wp_get_custom_css_post( $setting->stylesheet );
+ if ( $post && ! empty( $post->post_content_filtered ) ) {
+ $css = $post->post_content_filtered;
+ }
+
+ return $css;
+ }
+
+ /**
+ * Store the original pre-processed CSS in `post_content_filtered`
+ * and then store processed CSS in `post_content`.
+ *
+ * @param array $args Content post args.
+ * @param string $css Original CSS being updated.
+ * @param WP_Customize_Custom_CSS_Setting $setting Custom CSS Setting.
+ *
+ * @return mixed
+ */
+ public static function customize_update_custom_css_post_content_args( $args, $css, $setting ) {
+ // Find the current preprocessor.
+ $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
+ if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
+ return $args;
+ }
+
+ $preprocessor = $jetpack_custom_css['preprocessor'];
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+
+ // If it's empty, just return.
+ if ( empty( $preprocessor ) ) {
+ return $args;
+ }
+
+ if ( isset( $preprocessors[ $preprocessor ] ) ) {
+ $args['post_content_filtered'] = $css;
+ $args['post_content'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $css );
+ }
+
+ return $args;
+ }
+
+ /**
+ * Filter to handle the processing of preprocessed css on save.
+ *
+ * @param array $args Custom CSS options.
+ * @param string $stylesheet Original CSS to be updated.
+ *
+ * @return mixed
+ */
+ public static function update_custom_css_data( $args, $stylesheet ) {
+ // Find the current preprocessor.
+ $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
+ if ( empty( $jetpack_custom_css['preprocessor'] ) ) {
+ return $args;
+ }
+
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ $preprocessor = $jetpack_custom_css['preprocessor'];
+
+ // If we have a preprocessor specified ...
+ if ( isset( $preprocessors[ $preprocessor ] ) ) {
+ // And no other preprocessor has run ...
+ if ( empty( $args['preprocessed'] ) ) {
+ $args['preprocessed'] = $args['css'];
+ $args['css'] = call_user_func( $preprocessors[ $preprocessor ]['callback'], $args['css'] );
+ } else {
+ trigger_error( 'Jetpack CSS Preprocessor specified, but something else has already modified the argument.', E_USER_WARNING );
+ }
+ }
+
+ return $args;
+ }
+
+ /**
+ * When on the edit screen, make sure the custom content width
+ * setting is applied to the large image size.
+ *
+ * @param array $dims Array of image dimensions (width and height).
+ * @param string $size Size of the resulting image.
+ * @param null $context Context the image is being resized for. `edit` or `display`.
+ *
+ * @return array
+ */
+ static function editor_max_image_size( $dims, $size = 'medium', $context = null ) {
+ list( $width, $height ) = $dims;
+
+ if ( 'large' === $size && 'edit' === $context ) {
+ $width = Jetpack::get_content_width();
+ }
+
+ return array( $width, $height );
+ }
+
+ /**
+ * Override the content_width with a custom value if one is set.
+ *
+ * @param int $content_width Content Width value to be updated.
+ *
+ * @return int
+ */
+ static function jetpack_content_width( $content_width ) {
+ $custom_content_width = 0;
+
+ $jetpack_custom_css = get_theme_mod( 'jetpack_custom_css', array() );
+ if ( isset( $jetpack_custom_css['content_width'] ) ) {
+ $custom_content_width = $jetpack_custom_css['content_width'];
+ }
+
+ if ( $custom_content_width > 0 ) {
+ return $custom_content_width;
+ }
+
+ return $content_width;
+ }
+
+ /**
+ * Currently this filter function gets called on
+ * 'template_redirect' action and
+ * 'admin_init' action
+ */
+ static function set_content_width() {
+ // Don't apply this filter on the Edit CSS page.
+ if ( isset( $_GET['page'] ) && 'editcss' === $_GET['page'] && is_admin() ) {
+ return;
+ }
+
+ $GLOBALS['content_width'] = Jetpack::get_content_width();
+ }
+
+ /**
+ * Make sure the preprocessor we're saving is one we know about.
+ *
+ * @param string $preprocessor The preprocessor to sanitize.
+ *
+ * @return null|string
+ */
+ public static function sanitize_preprocessor( $preprocessor ) {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ if ( empty( $preprocessor ) || array_key_exists( $preprocessor, $preprocessors ) ) {
+ return $preprocessor;
+ }
+ return null;
+ }
+
+ /**
+ * Get the base10 intval.
+ *
+ * This is used as a setting's sanitize_callback; we can't use just plain
+ * intval because the second argument is not what intval() expects.
+ *
+ * @access public
+ *
+ * @param mixed $value Number to convert.
+ * @return int Integer.
+ */
+ public static function intval_base10( $value ) {
+ return intval( $value, 10 );
+ }
+
+ /**
+ * Add a footer action on revision.php to print some customizations for the theme switcher.
+ */
+ public static function load_revision_php() {
+ add_action( 'admin_footer', array( __CLASS__, 'revision_admin_footer' ) );
+ }
+
+ /**
+ * Print the theme switcher on revision.php and move it into place.
+ */
+ public static function revision_admin_footer() {
+ $post = get_post();
+ if ( 'custom_css' !== $post->post_type ) {
+ return;
+ }
+ $stylesheet = $post->post_title;
+ ?>
+<script type="text/html" id="tmpl-other-themes-switcher">
+ <?php self::revisions_switcher_box( $stylesheet ); ?>
+</script>
+<style>
+.other-themes-wrap {
+ float: right;
+ background-color: #fff;
+ -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ padding: 5px 10px;
+ margin-bottom: 10px;
+}
+.other-themes-wrap label {
+ display: block;
+ margin-bottom: 10px;
+}
+.other-themes-wrap select {
+ float: left;
+ width: 77%;
+}
+.other-themes-wrap button {
+ float: right;
+ width: 20%;
+}
+.revisions {
+ clear: both;
+}
+/* Hide the back-to-post link */
+.long-header + a {
+ display: none;
+}
+</style>
+<script>
+(function($){
+ var switcher = $('#tmpl-other-themes-switcher').html(),
+ qty = $( switcher ).find('select option').length,
+ $switcher;
+
+ if ( qty >= 3 ) {
+ $('h1.long-header').before( switcher );
+ $switcher = $('.other-themes-wrap');
+ $switcher.find('button').on('click', function(e){
+ e.preventDefault();
+ if ( $switcher.find('select').val() ) {
+ window.location.href = $switcher.find('select').val();
+ }
+ })
+ }
+})(jQuery);
+</script>
+ <?php
+ }
+
+ /**
+ * The HTML for the theme revision switcher box.
+ *
+ * @param string $stylesheet Stylesheet name.
+ */
+ public static function revisions_switcher_box( $stylesheet = '' ) {
+ $themes = self::get_all_themes_with_custom_css();
+ ?>
+ <div class="other-themes-wrap">
+ <label for="other-themes"><?php esc_html_e( 'Select another theme to view its custom CSS.', 'jetpack' ); ?></label>
+ <select id="other-themes">
+ <option value=""><?php esc_html_e( 'Select a theme&hellip;', 'jetpack' ); ?></option>
+ <?php
+ foreach ( $themes as $theme_stylesheet => $data ) {
+ $revisions = wp_get_post_revisions( $data['post']->ID, array( 'posts_per_page' => 1 ) );
+ if ( ! $revisions ) {
+ ?>
+ <option value="<?php echo esc_url( add_query_arg( 'id', $data['post']->ID, menu_page_url( 'editcss', 0 ) ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
+ <?php echo esc_html( $data['label'] ); ?>
+ <?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
+ <?php
+ continue;
+ }
+ $revision = array_shift( $revisions );
+ ?>
+ <option value="<?php echo esc_url( get_edit_post_link( $revision->ID ) ); ?>" <?php disabled( $stylesheet, $theme_stylesheet ); ?>>
+ <?php echo esc_html( $data['label'] ); ?>
+ <?php printf( esc_html__( '(modified %s ago)', 'jetpack' ), human_time_diff( strtotime( $data['post']->post_modified_gmt ) ) ); ?></option>
+ <?php
+ }
+ ?>
+ </select>
+ <button class="button" id="other_theme_custom_css_switcher"><?php esc_html_e( 'Switch', 'jetpack' ); ?></button>
+ </div>
+ <?php
+ }
+}
+
+Jetpack_Custom_CSS_Enhancements::add_hooks();
+
+if ( ! function_exists( 'safecss_class' ) ) :
+ /**
+ * Load in the class only when needed. Makes lighter load by having one less class in memory.
+ */
+ function safecss_class() {
+ // Wrapped so we don't need the parent class just to load the plugin.
+ if ( class_exists( 'safecss' ) ) {
+ return;
+ }
+
+ require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' );
+
+ /**
+ * Class safecss
+ */
+ class safecss extends csstidy_optimise {
+
+ /**
+ * Optimises $css after parsing.
+ */
+ function postparse() {
+
+ /** This action is documented in modules/custom-css/custom-css.php */
+ do_action( 'csstidy_optimize_postparse', $this );
+
+ return parent::postparse();
+ }
+
+ /**
+ * Optimises a sub-value.
+ */
+ function subvalue() {
+
+ /** This action is documented in modules/custom-css/custom-css.php */
+ do_action( 'csstidy_optimize_subvalue', $this );
+
+ return parent::subvalue();
+ }
+ }
+ }
+endif;
diff --git a/plugins/jetpack/modules/custom-css/custom-css.php b/plugins/jetpack/modules/custom-css/custom-css.php
new file mode 100644
index 00000000..6229014b
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css.php
@@ -0,0 +1,1866 @@
+<?php
+
+class Jetpack_Custom_CSS {
+ static function init() {
+ add_action( 'switch_theme', array( __CLASS__, 'reset' ) );
+ add_action( 'wp_restore_post_revision', array( __CLASS__, 'restore_revision' ), 10, 2 );
+
+ // Save revisions for posts of type safecss.
+ add_action( 'load-revision.php', array( __CLASS__, 'add_revision_redirect' ) );
+
+ // Override the edit link, the default link causes a redirect loop
+ add_filter( 'get_edit_post_link', array( __CLASS__, 'revision_post_link' ), 10, 3 );
+
+ // Overwrite the content width global variable if one is set in the custom css
+ add_action( 'template_redirect', array( __CLASS__, 'set_content_width' ) );
+ add_action( 'admin_init', array( __CLASS__, 'set_content_width' ) );
+
+ if ( ! is_admin() )
+ add_filter( 'stylesheet_uri', array( __CLASS__, 'style_filter' ) );
+
+ define(
+ 'SAFECSS_USE_ACE',
+ ! jetpack_is_mobile() &&
+ ! Jetpack_User_Agent_Info::is_ipad() &&
+ /**
+ * Should the Custom CSS module use ACE to process CSS.
+ * @see http://ace.c9.io/
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param bool true Use ACE to process the Custom CSS. Default to true.
+ */
+ apply_filters( 'safecss_use_ace', true )
+ );
+
+ // Register safecss as a custom post_type
+ // Explicit capability definitions are largely unnecessary because the posts are manipulated in code via an options page, managing CSS revisions does check the capabilities, so let's ensure that the proper caps are checked.
+ register_post_type( 'safecss', array(
+ // These are the defaults
+ // 'exclude_from_search' => true,
+ // 'public' => false,
+ // 'publicly_queryable' => false,
+ // 'show_ui' => false,
+ 'supports' => array( 'revisions' ),
+ 'label' => 'Custom CSS',
+ 'can_export' => false,
+ 'rewrite' => false,
+ 'capabilities' => array(
+ 'edit_post' => 'edit_theme_options',
+ 'read_post' => 'read',
+ 'delete_post' => 'edit_theme_options',
+ 'edit_posts' => 'edit_theme_options',
+ 'edit_others_posts' => 'edit_theme_options',
+ 'publish_posts' => 'edit_theme_options',
+ 'read_private_posts' => 'read'
+ )
+ ) );
+
+ // Short-circuit WP if this is a CSS stylesheet request
+ if ( isset( $_GET['custom-css'] ) ) {
+ header( 'Content-Type: text/css', true, 200 );
+ header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 31536000) . ' GMT' ); // 1 year
+ Jetpack_Custom_CSS::print_css();
+ exit;
+ }
+
+ add_action( 'admin_enqueue_scripts', array( 'Jetpack_Custom_CSS', 'enqueue_scripts' ) );
+
+ if ( isset( $_GET['page'] ) && 'editcss' == $_GET['page'] && is_admin() ) {
+ // Do migration routine if necessary
+ Jetpack_Custom_CSS::upgrade();
+
+ /**
+ * Allows additional work when migrating safecss from wp_options to wp_post.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ */
+ do_action( 'safecss_migrate_post' );
+ }
+
+ /**
+ * Never embed the style in the head on wpcom.
+ * Yes, this filter should be added to an unsynced file on wpcom, but
+ * there is no good syntactically-correct location to put it yet.
+ * @link https://github.com/Automattic/jetpack/commit/a1be114e9179f64d147124727a58e2cf76c7e5a1#commitcomment-7763921
+ */
+ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+ add_filter( 'safecss_embed_style', '__return_false' );
+ } else {
+ add_filter( 'safecss_embed_style', array( 'Jetpack_Custom_CSS', 'should_we_inline_custom_css' ), 10, 2 );
+ }
+
+ add_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 );
+
+ add_filter( 'jetpack_content_width', array( 'Jetpack_Custom_CSS', 'jetpack_content_width' ) );
+ add_filter( 'editor_max_image_size', array( 'Jetpack_Custom_CSS', 'editor_max_image_size' ), 10, 3 );
+
+ if ( !current_user_can( 'switch_themes' ) && !is_super_admin() )
+ return;
+
+ add_action( 'admin_menu', array( 'Jetpack_Custom_CSS', 'menu' ) );
+
+ if ( isset( $_POST['safecss'] ) && false == strstr( $_SERVER[ 'REQUEST_URI' ], 'options.php' ) ) {
+ check_admin_referer( 'safecss' );
+
+ $save_result = self::save( array(
+ 'css' => stripslashes( $_POST['safecss'] ),
+ 'is_preview' => isset( $_POST['action'] ) && $_POST['action'] == 'preview',
+ 'preprocessor' => isset( $_POST['custom_css_preprocessor'] ) ? $_POST['custom_css_preprocessor'] : '',
+ 'add_to_existing' => isset( $_POST['add_to_existing'] ) ? $_POST['add_to_existing'] == 'true' : true,
+ 'content_width' => isset( $_POST['custom_content_width'] ) ? $_POST['custom_content_width'] : false,
+ ) );
+
+ if ( $_POST['action'] == 'preview' ) {
+ wp_safe_redirect( add_query_arg( 'csspreview', 'true', get_option( 'home' ) ) );
+ exit;
+ }
+
+ if ( $save_result )
+ add_action( 'admin_notices', array( 'Jetpack_Custom_CSS', 'saved_message' ) );
+ }
+
+ // Prevent content filters running on CSS when restoring revisions
+ if ( isset( $_REQUEST[ 'action' ] ) && 'restore' === $_REQUEST[ 'action' ] && false !== strstr( $_SERVER[ 'REQUEST_URI' ], 'revision.php' ) ) {
+ $parent_post = get_post( wp_get_post_parent_id( intval( $_REQUEST[ 'revision' ] ) ) );
+ if ( $parent_post && ! is_wp_error( $parent_post ) && 'safecss' === $parent_post->post_type ) {
+ // Remove wp_filter_post_kses, this causes CSS escaping issues
+ remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
+ remove_all_filters( 'content_save_pre' );
+ }
+ }
+
+ // Modify all internal links so that preview state persists
+ if ( Jetpack_Custom_CSS::is_preview() )
+ ob_start( array( 'Jetpack_Custom_CSS', 'buffer' ) );
+ }
+
+ /**
+ * Save new custom CSS. This should be the entry point for any third-party code using Jetpack_Custom_CSS
+ * to save CSS.
+ *
+ * @param array $args Array of arguments:
+ * string $css The CSS (or LESS or Sass)
+ * bool $is_preview Whether this CSS is preview or published
+ * string preprocessor Which CSS preprocessor to use
+ * bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it.
+ * int $content_width A custom $content_width to go along with this CSS.
+ * @return int The post ID of the saved Custom CSS post.
+ */
+ public static function save( $args = array() ) {
+ $defaults = array(
+ 'css' => '',
+ 'is_preview' => false,
+ 'preprocessor' => '',
+ 'add_to_existing' => true,
+ 'content_width' => false,
+ );
+
+ $args = wp_parse_args( $args, $defaults );
+
+ if ( $args['content_width'] && intval( $args['content_width']) > 0 && ( ! isset( $GLOBALS['content_width'] ) || $args['content_width'] != $GLOBALS['content_width'] ) )
+ $args['content_width'] = intval( $args['content_width'] );
+ else
+ $args['content_width'] = false;
+
+ // Remove wp_filter_post_kses, this causes CSS escaping issues
+ remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
+ remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
+ remove_all_filters( 'content_save_pre' );
+
+ /**
+ * Fires prior to saving custom css values. Necessitated because the
+ * core WordPress save_pre filters were removed:
+ * - content_save_pre
+ * - content_filtered_save_pre
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param array $args {
+ * Array of custom CSS arguments.
+ * @type string $css The CSS (or LESS or Sass).
+ * @type bool $is_preview Whether this CSS is preview or published.
+ * @type string preprocessor Which CSS preprocessor to use.
+ * @type bool $add_to_existing Whether this CSS replaces the theme's CSS or supplements it.
+ * @type int $content_width A custom $content_width to go along with this CSS.
+ * }
+ */
+ do_action( 'safecss_save_pre', $args );
+
+ $warnings = array();
+
+ safecss_class();
+ $csstidy = new csstidy();
+ $csstidy->optimise = new safecss( $csstidy );
+
+ $csstidy->set_cfg( 'remove_bslash', false );
+ $csstidy->set_cfg( 'compress_colors', false );
+ $csstidy->set_cfg( 'compress_font-weight', false );
+ $csstidy->set_cfg( 'optimise_shorthands', 0 );
+ $csstidy->set_cfg( 'remove_last_;', false );
+ $csstidy->set_cfg( 'case_properties', false );
+ $csstidy->set_cfg( 'discard_invalid_properties', true );
+ $csstidy->set_cfg( 'css_level', 'CSS3.0' );
+ $csstidy->set_cfg( 'preserve_css', true );
+ $csstidy->set_cfg( 'template', dirname( __FILE__ ) . '/csstidy/wordpress-standard.tpl' );
+
+ $css = $orig = $args['css'];
+
+ $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $prev = $css );
+ // prevent content: '\3434' from turning into '\\3434'
+ $css = str_replace( array( '\'\\\\', '"\\\\' ), array( '\'\\', '"\\' ), $css );
+
+ if ( $css != $prev )
+ $warnings[] = 'preg_replace found stuff';
+
+ // Some people put weird stuff in their CSS, KSES tends to be greedy
+ $css = str_replace( '<=', '&lt;=', $css );
+ // Why KSES instead of strip_tags? Who knows?
+ $css = wp_kses_split( $prev = $css, array(), array() );
+ $css = str_replace( '&gt;', '>', $css ); // kses replaces lone '>' with &gt;
+ // Why both KSES and strip_tags? Because we just added some '>'.
+ $css = strip_tags( $css );
+
+ if ( $css != $prev )
+ $warnings[] = 'kses found stuff';
+
+ // if we're not using a preprocessor
+ if ( ! $args['preprocessor'] ) {
+
+ /**
+ * Fires before parsing the css with CSSTidy, but only if
+ * the preprocessor is not configured for use.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param obj $csstidy The csstidy object.
+ * @param string $css Custom CSS.
+ * @param array $args Array of custom CSS arguments.
+ */
+ do_action( 'safecss_parse_pre', $csstidy, $css, $args );
+
+ $csstidy->parse( $css );
+
+ /**
+ * Fires after parsing the css with CSSTidy, but only if
+ * the preprocessor is not cinfigured for use.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param obj $csstidy The csstidy object.
+ * @param array $warnings Array of warnings.
+ * @param array $args Array of custom CSS arguments.
+ */
+ do_action( 'safecss_parse_post', $csstidy, $warnings, $args );
+
+ $css = $csstidy->print->plain();
+ }
+
+ if ( $args['add_to_existing'] )
+ $add_to_existing = 'yes';
+ else
+ $add_to_existing = 'no';
+
+ if ( $args['is_preview'] || Jetpack_Custom_CSS::is_freetrial() ) {
+ // Save the CSS
+ $safecss_revision_id = Jetpack_Custom_CSS::save_revision( $css, true, $args['preprocessor'] );
+
+ // Cache Buster
+ update_option( 'safecss_preview_rev', intval( get_option( 'safecss_preview_rev' ) ) + 1);
+
+ update_metadata( 'post', $safecss_revision_id, 'custom_css_add', $add_to_existing );
+ update_metadata( 'post', $safecss_revision_id, 'content_width', $args['content_width'] );
+ update_metadata( 'post', $safecss_revision_id, 'custom_css_preprocessor', $args['preprocessor'] );
+
+ delete_option( 'safecss_add' );
+ delete_option( 'safecss_content_width' );
+
+ if ( $args['is_preview'] ) {
+ return $safecss_revision_id;
+ }
+
+ /**
+ * Fires after saving Custom CSS.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ */
+ do_action( 'safecss_save_preview_post' );
+ }
+
+ // Save the CSS
+ $safecss_post_id = Jetpack_Custom_CSS::save_revision( $css, false, $args['preprocessor'] );
+
+ $safecss_post_revision = Jetpack_Custom_CSS::get_current_revision();
+
+ update_option( 'safecss_rev', intval( get_option( 'safecss_rev' ) ) + 1 );
+
+ update_post_meta( $safecss_post_id, 'custom_css_add', $add_to_existing );
+ update_post_meta( $safecss_post_id, 'content_width', $args['content_width'] );
+ update_post_meta( $safecss_post_id, 'custom_css_preprocessor', $args['preprocessor'] );
+
+ delete_option( 'safecss_add' );
+ delete_option( 'safecss_content_width' );
+
+ update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_add', $add_to_existing );
+ update_metadata( 'post', $safecss_post_revision['ID'], 'content_width', $args['content_width'] );
+ update_metadata( 'post', $safecss_post_revision['ID'], 'custom_css_preprocessor', $args['preprocessor'] );
+
+ delete_option( 'safecss_preview_add' );
+
+ return $safecss_post_id;
+ }
+
+ /**
+ * Get the published custom CSS post.
+ *
+ * @return array
+ */
+ static function get_post() {
+ $custom_css_post_id = Jetpack_Custom_CSS::post_id();
+
+ if ( $custom_css_post_id )
+ return get_post( $custom_css_post_id, ARRAY_A );
+
+ return array();
+ }
+
+ /**
+ * Get the post ID of the published custom CSS post.
+ *
+ * @return int|bool The post ID if it exists; false otherwise.
+ */
+ static function post_id() {
+ /**
+ * Filter the ID of the post where Custom CSS is stored, before the ID is retrieved.
+ *
+ * If the callback function returns a non-null value, then post_id() will immediately
+ * return that value, instead of retrieving the normal post ID.
+ *
+ * @module custom-css
+ *
+ * @since 3.8.1
+ *
+ * @param null null The ID to return instead of the normal ID.
+ */
+ $custom_css_post_id = apply_filters( 'jetpack_custom_css_pre_post_id', null );
+ if ( ! is_null( $custom_css_post_id ) ) {
+ return $custom_css_post_id;
+ }
+
+ $custom_css_post_id = wp_cache_get( 'custom_css_post_id' );
+
+ if ( false === $custom_css_post_id ) {
+ $custom_css_posts = get_posts( array(
+ 'posts_per_page' => 1,
+ 'post_type' => 'safecss',
+ 'post_status' => 'publish',
+ 'orderby' => 'date',
+ 'order' => 'DESC'
+ ) );
+
+ if ( count( $custom_css_posts ) > 0 )
+ $custom_css_post_id = $custom_css_posts[0]->ID;
+ else
+ $custom_css_post_id = 0;
+
+ // Save post_id=0 to note that no safecss post exists.
+ wp_cache_set( 'custom_css_post_id', $custom_css_post_id );
+ }
+
+ if ( ! $custom_css_post_id )
+ return false;
+
+ return $custom_css_post_id;
+ }
+
+ /**
+ * Get the current revision of the original safecss record
+ *
+ * @return object
+ */
+ static function get_current_revision() {
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+
+ if ( empty( $safecss_post ) ) {
+ return false;
+ }
+
+ $revisions = wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'DESC' ) );
+
+ // Empty array if no revisions exist
+ if ( empty( $revisions ) ) {
+ // Return original post
+ return $safecss_post;
+ } else {
+ // Return the first entry in $revisions, this will be the current revision
+ $current_revision = get_object_vars( array_shift( $revisions ) );
+ return $current_revision;
+ }
+ }
+
+ /**
+ * Save new revision of CSS
+ * Checks to see if content was modified before really saving
+ *
+ * @param string $css
+ * @param bool $is_preview
+ * @return bool|int If nothing was saved, returns false. If a post
+ * or revision was saved, returns the post ID.
+ */
+ static function save_revision( $css, $is_preview = false, $preprocessor = '' ) {
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+
+ $compressed_css = Jetpack_Custom_CSS::minify( $css, $preprocessor );
+
+ // If null, there was no original safecss record, so create one
+ if ( null == $safecss_post ) {
+ if ( ! $css )
+ return false;
+
+ $post = array();
+ $post['post_content'] = wp_slash( $css );
+ $post['post_title'] = 'safecss';
+ $post['post_status'] = 'publish';
+ $post['post_type'] = 'safecss';
+ $post['post_content_filtered'] = wp_slash( $compressed_css );
+
+ // Set excerpt to current theme, for display in revisions list
+ $current_theme = wp_get_theme();
+ $post['post_excerpt'] = $current_theme->Name;
+
+ // Insert the CSS into wp_posts
+ $post_id = wp_insert_post( $post );
+ wp_cache_set( 'custom_css_post_id', $post_id );
+ return $post_id;
+ }
+
+ // Update CSS in post array with new value passed to this function
+ $safecss_post['post_content'] = $css;
+ $safecss_post['post_content_filtered'] = $compressed_css;
+
+ // Set excerpt to current theme, for display in revisions list
+ $current_theme = wp_get_theme();
+ $safecss_post['post_excerpt'] = $current_theme->Name;
+
+ // Don't carry over last revision's timestamps, otherwise revisions all have matching timestamps
+ unset( $safecss_post['post_date'] );
+ unset( $safecss_post['post_date_gmt'] );
+ unset( $safecss_post['post_modified'] );
+ unset( $safecss_post['post_modified_gmt'] );
+
+ // Do not update post if we are only saving a preview
+ if ( false === $is_preview ) {
+ $safecss_post['post_content'] = wp_slash( $safecss_post['post_content'] );
+ $safecss_post['post_content_filtered'] = wp_slash( $safecss_post['post_content_filtered'] );
+ $post_id = wp_update_post( $safecss_post );
+ wp_cache_set( 'custom_css_post_id', $post_id );
+ return $post_id;
+ }
+ else if ( ! defined( 'DOING_MIGRATE' ) ) {
+ return _wp_put_post_revision( $safecss_post );
+ }
+ }
+
+ static function skip_stylesheet() {
+ /**
+ * Prevent the Custom CSS stylesheet from being enqueued.
+ *
+ * @module custom-css
+ *
+ * @since 2.2.1
+ *
+ * @param null Should the stylesheet be skipped. Default to null. Anything else will force the stylesheet to be skipped.
+ */
+ $skip_stylesheet = apply_filters( 'safecss_skip_stylesheet', null );
+
+ if ( null !== $skip_stylesheet ) {
+ return $skip_stylesheet;
+ } elseif ( Jetpack_Custom_CSS::is_customizer_preview() ) {
+ return false;
+ } else {
+ if ( Jetpack_Custom_CSS::is_preview() ) {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+
+ if ( $safecss_post )
+ return (bool) ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) == 'no' );
+ else
+ return (bool) ( get_option( 'safecss_preview_add' ) == 'no' );
+ }
+ else {
+ $custom_css_post_id = Jetpack_Custom_CSS::post_id();
+
+ if ( $custom_css_post_id ) {
+ $custom_css_add = get_post_meta( $custom_css_post_id, 'custom_css_add', true );
+
+ // It is possible for the CSS to be stored in a post but for the safecss_add option
+ // to have not been upgraded yet if the user hasn't opened their Custom CSS editor
+ // since October 2012.
+ if ( ! empty( $custom_css_add ) )
+ return (bool) ( $custom_css_add === 'no' );
+ }
+
+ return (bool) ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_add', '' ) == 'no' );
+ }
+ }
+ }
+
+ static function is_preview() {
+ return isset( $_GET['csspreview'] ) && $_GET['csspreview'] === 'true';
+ }
+
+ /**
+ * Currently this filter function gets called on
+ * 'template_redirect' action and
+ * 'admin_init' action
+ */
+ static function set_content_width(){
+ // Don't apply this filter on the Edit CSS page
+ if ( isset( $_GET ) && isset( $_GET['page'] ) && 'editcss' == $_GET['page'] && is_admin() ) {
+ return;
+ }
+
+ $GLOBALS['content_width'] = Jetpack::get_content_width();
+ }
+
+ /*
+ * False when the site has the Custom Design upgrade.
+ * Used only on WordPress.com.
+ */
+ static function is_freetrial() {
+ /**
+ * Determine if a WordPress.com site uses a Free trial of the Custom Design Upgrade.
+ * Used only on WordPress.com.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param bool false Does the site use a Free trial of the Custom Design Upgrade. Default to false.
+ */
+ return apply_filters( 'safecss_is_freetrial', false );
+ }
+
+ static function get_preprocessor_key() {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+ return get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true );
+ }
+
+ static function get_preprocessor() {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ $selected_preprocessor_key = self::get_preprocessor_key();
+ $selected_preprocessor = isset( $preprocessors[ $selected_preprocessor_key ] ) ? $preprocessors[ $selected_preprocessor_key ] : null;
+ return $selected_preprocessor;
+ }
+
+ static function get_css( $compressed = false ) {
+ /**
+ * Filter the Custom CSS returned.
+ * Can be used to return an error, or no CSS at all.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param bool false Should we return an error instead of the Custom CSS. Default to false.
+ */
+ $default_css = apply_filters( 'safecss_get_css_error', false );
+
+ if ( $default_css !== false )
+ return $default_css;
+
+ $option = ( Jetpack_Custom_CSS::is_preview() || Jetpack_Custom_CSS::is_freetrial() ) ? 'safecss_preview' : 'safecss';
+ $css = '';
+
+ if ( 'safecss' == $option ) {
+ // Don't bother checking for a migrated 'safecss' option if it never existed.
+ if ( false === get_option( 'safecss' ) || get_option( 'safecss_revision_migrated' ) ) {
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+ if ( ! empty( $safecss_post ) ) {
+ $css = ( $compressed && $safecss_post['post_content_filtered'] ) ? $safecss_post['post_content_filtered'] : $safecss_post['post_content'];
+ }
+ } else {
+ $current_revision = Jetpack_Custom_CSS::get_current_revision();
+ if ( false === $current_revision ) {
+ $css = '';
+ } else {
+ $css = ( $compressed && $current_revision['post_content_filtered'] ) ? $current_revision['post_content_filtered'] : $current_revision['post_content'];
+ }
+ }
+
+ // Fix for un-migrated Custom CSS
+ if ( empty( $safecss_post ) ) {
+ $_css = get_option( 'safecss' );
+ if ( !empty( $_css ) ) {
+ $css = $_css;
+ }
+ }
+ }
+ else if ( 'safecss_preview' == $option ) {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+ $css = $safecss_post['post_content'];
+ $css = Jetpack_Custom_CSS::minify( $css, get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true ) );
+ }
+
+ $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css );
+
+ if ( empty( $css ) ) {
+ $css = "/*\n"
+ . wordwrap(
+ /**
+ * Filter the default message displayed in the Custom CSS editor.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $str Default Custom CSS editor content.
+ */
+ apply_filters(
+ 'safecss_default_css',
+ __(
+ "Welcome to Custom CSS!\n\nTo learn how this works, see http://wp.me/PEmnE-Bt",
+ 'jetpack'
+ )
+ )
+ )
+ . "\n*/";
+ }
+
+ /**
+ * Filter the Custom CSS returned from the editor.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $css Custom CSS.
+ */
+ $css = apply_filters( 'safecss_css', $css );
+
+ return $css;
+ }
+
+ static function replace_insecure_urls( $css ) {
+ if ( ! function_exists( '_sa_get_frontend_https_url_replacement_map' ) ) {
+ return $css;
+ }
+ list( $http_urls, $secure_urls ) = _sa_get_frontend_https_url_replacement_map();
+
+ return str_replace( $http_urls, $secure_urls, $css );
+ }
+
+ static function print_css() {
+
+ /**
+ * Fires right before printing the custom CSS inside the <head> element.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ */
+ do_action( 'safecss_print_pre' );
+ $css = Jetpack_Custom_CSS::get_css( true );
+ echo self::replace_insecure_urls( $css );
+ }
+
+ static function should_we_inline_custom_css( $should_we, $css ) {
+ // If the CSS is less than 2,000 characters, inline it! otherwise return what was passed in.
+ return ( strlen( $css ) < 2000 ) ? true : $should_we;
+ }
+
+ static function link_tag() {
+ global $blog_id, $current_blog;
+
+ if (
+ /**
+ * Do not include any CSS on the page if the CSS includes an error.
+ * Setting this filter to true stops any Custom CSS from being enqueued.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param bool false Does the CSS include an error. Default to false.
+ */
+ apply_filters( 'safecss_style_error', false )
+ ) {
+ return;
+ }
+
+ if ( ! is_super_admin() && isset( $current_blog ) && ( 1 == $current_blog->spam || 1 == $current_blog->deleted ) )
+ return;
+
+ if ( Jetpack_Custom_CSS::is_customizer_preview() )
+ return;
+
+ $css = '';
+ $option = Jetpack_Custom_CSS::is_preview() ? 'safecss_preview' : 'safecss';
+
+ if ( 'safecss' == $option ) {
+ if ( Jetpack_Options::get_option_and_ensure_autoload( 'safecss_revision_migrated', '0' ) ) {
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+
+ if ( ! empty( $safecss_post['post_content'] ) ) {
+ $css = $safecss_post['post_content'];
+ }
+ } else {
+ $current_revision = Jetpack_Custom_CSS::get_current_revision();
+
+ if ( ! empty( $current_revision['post_content'] ) ) {
+ $css = $current_revision['post_content'];
+ }
+ }
+
+ // Fix for un-migrated Custom CSS
+ if ( empty( $safecss_post ) ) {
+ $_css = Jetpack_Options::get_option_and_ensure_autoload( 'safecss', '' );
+ if ( !empty( $_css ) ) {
+ $css = $_css;
+ }
+ }
+ }
+
+ if ( 'safecss_preview' == $option ) {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+
+ if ( !empty( $safecss_post['post_content'] ) ) {
+ $css = $safecss_post['post_content'];
+ }
+ }
+
+ $css = str_replace( array( '\\\00BB \\\0020', '\0BB \020', '0BB 020' ), '\00BB \0020', $css );
+
+ if ( $css == '' )
+ return;
+
+ if (
+ /**
+ * Allow inserting CSS inline instead of through a separate file.
+ *
+ * @module custom-css
+ *
+ * @since 3.4.0
+ *
+ * @param bool false Should the CSS be added inline instead of through a separate file. Default to false.
+ * @param string $css Custom CSS.
+ */
+ apply_filters( 'safecss_embed_style', false, $css )
+ ) {
+
+ echo "\r\n" . '<style id="custom-css-css">' . Jetpack_Custom_CSS::get_css( true ) . "</style>\r\n";
+
+ } else {
+
+ $href = home_url( '/' );
+ $href = add_query_arg( 'custom-css', 1, $href );
+ $href = add_query_arg( 'csblog', $blog_id, $href );
+ $href = add_query_arg( 'cscache', 6, $href );
+ $href = add_query_arg( 'csrev', (int) get_option( $option . '_rev' ), $href );
+
+ /**
+ * Filter the Custom CSS link enqueued in the head.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $href Custom CSS link enqueued in the head.
+ * @param string $blog_id Blog ID.
+ */
+ $href = apply_filters( 'safecss_href', $href, $blog_id );
+
+ if ( Jetpack_Custom_CSS::is_preview() )
+ $href = add_query_arg( 'csspreview', 'true', $href );
+
+ ?>
+ <link rel="stylesheet" id="custom-css-css" type="text/css" href="<?php echo esc_url( $href ); ?>" />
+ <?php
+
+ }
+
+ /**
+ * Fires after creating the <link> in the <head> element for the custom css stylesheet.
+ *
+ * @module custom-css
+ *
+ * @since 2.2.2
+ */
+ do_action( 'safecss_link_tag_post' );
+ }
+
+ static function style_filter( $current ) {
+ if ( Jetpack_Custom_CSS::is_freetrial() && ( ! Jetpack_Custom_CSS::is_preview() || ! current_user_can( 'switch_themes' ) ) )
+ return $current;
+ else if ( Jetpack_Custom_CSS::skip_stylesheet() )
+ /**
+ * Filter the default blank Custom CSS URL.
+ *
+ * @module custom-css
+ *
+ * @since 2.2.1
+ *
+ * @param string $url Default blank Custom CSS URL.
+ */
+ return apply_filters( 'safecss_style_filter_url', plugins_url( 'custom-css/css/blank.css', __FILE__ ) );
+
+ return $current;
+ }
+
+ static function buffer( $html ) {
+ $html = str_replace( '</body>', Jetpack_Custom_CSS::preview_flag(), $html );
+ return preg_replace_callback( '!href=([\'"])(.*?)\\1!', array( 'Jetpack_Custom_CSS', 'preview_links' ), $html );
+ }
+
+ static function preview_links( $matches ) {
+ if ( 0 !== strpos( $matches[2], get_option( 'home' ) ) )
+ return $matches[0];
+
+ $link = wp_specialchars_decode( $matches[2] );
+ $link = add_query_arg( 'csspreview', 'true', $link );
+ $link = esc_url( $link );
+ return "href={$matches[1]}$link{$matches[1]}";
+ }
+
+ /**
+ * Places a black bar above every preview page
+ */
+ static function preview_flag() {
+ if ( is_admin() )
+ return;
+
+ $message = esc_html__( 'Preview: changes must be saved or they will be lost', 'jetpack' );
+ /**
+ * Filter the Preview message displayed on the site when previewing custom CSS, before to save it.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $message Custom CSS preview message.
+ */
+ $message = apply_filters( 'safecss_preview_message', $message );
+
+ $preview_flag_js = "var flag = document.createElement('div');
+ flag.innerHTML = " . json_encode( $message ) . ";
+ flag.style.background = '#FF6600';
+ flag.style.color = 'white';
+ flag.style.textAlign = 'center';
+ flag.style.fontSize = '15px';
+ flag.style.padding = '2px';
+ flag.style.fontFamily = 'sans-serif';
+ document.body.style.paddingTop = '0px';
+ document.body.insertBefore(flag, document.body.childNodes[0]);
+ ";
+
+ /**
+ * Filter the Custom CSS preview message JS styling.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $preview_flag_js Custom CSS preview message JS styling.
+ */
+ $preview_flag_js = apply_filters( 'safecss_preview_flag_js', $preview_flag_js );
+ if ( $preview_flag_js ) {
+ $preview_flag_js = '<script type="text/javascript">
+ // <![CDATA[
+ ' . $preview_flag_js . '
+ // ]]>
+ </script>';
+ }
+
+ return $preview_flag_js;
+ }
+
+ static function menu() {
+ $parent = 'themes.php';
+ $title = __( 'Edit CSS', 'jetpack' );
+ $hook = add_theme_page( $title, $title, 'edit_theme_options', 'editcss', array( 'Jetpack_Custom_CSS', 'admin' ) );
+
+ add_action( "load-revision.php", array( 'Jetpack_Custom_CSS', 'prettify_post_revisions' ) );
+ add_action( "load-$hook", array( 'Jetpack_Custom_CSS', 'update_title' ) );
+ }
+
+ /**
+ * Adds a menu item in the appearance section for this plugin's administration
+ * page. Also adds hooks to enqueue the CSS and JS for the admin page.
+ */
+ static function update_title() {
+ global $title;
+ $title = __( 'CSS', 'jetpack' );
+ }
+
+ static function prettify_post_revisions() {
+ add_filter( 'the_title', array( 'Jetpack_Custom_CSS', 'post_title' ), 10, 2 );
+ }
+
+ static function post_title( $title, $post_id ) {
+ if ( !$post_id = (int) $post_id ) {
+ return $title;
+ }
+
+ if ( !$post = get_post( $post_id ) ) {
+ return $title;
+ }
+
+ if ( 'safecss' != $post->post_type ) {
+ return $title;
+ }
+
+ return __( 'Custom CSS Stylesheet', 'jetpack' );
+ }
+
+ static function enqueue_scripts( $hook ) {
+ if ( 'appearance_page_editcss' != $hook )
+ return;
+
+ wp_enqueue_script( 'postbox' );
+ wp_enqueue_script(
+ 'custom-css-editor',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/css-editor.min.js',
+ 'modules/custom-css/custom-css/js/css-editor.js'
+ ),
+ 'jquery',
+ '20130325',
+ true
+ );
+ wp_enqueue_style( 'custom-css-editor', plugins_url( 'custom-css/css/css-editor.css', __FILE__ ) );
+
+ if ( defined( 'SAFECSS_USE_ACE' ) && SAFECSS_USE_ACE ) {
+ wp_register_style( 'jetpack-css-codemirror', plugins_url( 'custom-css/css/codemirror.css', __FILE__ ), array(), '20120905' );
+ wp_enqueue_style( 'jetpack-css-use-codemirror', plugins_url( 'custom-css/css/use-codemirror.css', __FILE__ ), array( 'jetpack-css-codemirror' ), '20120905' );
+
+ wp_register_script( 'jetpack-css-codemirror', plugins_url( 'custom-css/js/codemirror.min.js', __FILE__ ), array(), '3.16', true );
+ wp_enqueue_script(
+ 'jetpack-css-use-codemirror',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/custom-css/custom-css/js/use-codemirror.min.js',
+ 'modules/custom-css/custom-css/js/use-codemirror.js'
+ ),
+ array( 'jquery', 'underscore', 'jetpack-css-codemirror' ),
+ '20131009',
+ true
+ );
+ }
+ }
+
+ static function saved_message() {
+ echo '<div id="message" class="updated fade"><p><strong>' . __( 'Stylesheet saved.', 'jetpack' ) . '</strong></p></div>';
+ }
+
+ static function admin() {
+ add_meta_box( 'submitdiv', __( 'Publish', 'jetpack' ), array( __CLASS__, 'publish_box' ), 'editcss', 'side' );
+ add_action( 'custom_css_submitbox_misc_actions', array( __CLASS__, 'content_width_settings' ) );
+
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+
+ if ( ! empty( $safecss_post ) && 0 < $safecss_post['ID'] && wp_get_post_revisions( $safecss_post['ID'], array( 'posts_per_page' => 1 ) ) )
+ add_meta_box( 'revisionsdiv', __( 'CSS Revisions', 'jetpack' ), array( __CLASS__, 'revisions_meta_box' ), 'editcss', 'side' );
+ ?>
+ <div class="wrap">
+ <?php
+
+ /**
+ * Fires right before the custom css page begins.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ */
+ do_action( 'custom_design_header' );
+
+ ?>
+ <h1><?php _e( 'CSS Stylesheet Editor', 'jetpack' ); ?></h1>
+ <form id="safecssform" action="" method="post">
+ <?php wp_nonce_field( 'safecss' ) ?>
+ <?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
+ <?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
+ <input type="hidden" name="action" value="save" />
+ <div id="poststuff">
+ <p class="css-support">
+ <?php
+ /**
+ * Filter the intro text appearing above the Custom CSS Editor.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $str Intro text appearing above the Custom CSS editor.
+ */
+ echo apply_filters( 'safecss_intro_text', __( 'New to CSS? Start with a <a href="http://www.htmldog.com/guides/cssbeginner/" rel="noopener noreferrer" target="_blank">beginner tutorial</a>. Questions?
+ Ask in the <a href="https://wordpress.org/support/forum/themes-and-templates" rel="noopener noreferrer" target="_blank">Themes and Templates forum</a>.', 'jetpack' ) );
+ ?></p>
+ <p class="css-support"><?php echo __( 'Note: Custom CSS will be reset when changing themes.', 'jetpack' ); ?></p>
+
+ <div id="post-body" class="metabox-holder columns-2">
+ <div id="post-body-content">
+ <div class="postarea">
+ <textarea id="safecss" name="safecss"<?php if ( SAFECSS_USE_ACE ) echo ' class="hide-if-js"'; ?>><?php echo esc_textarea( Jetpack_Custom_CSS::get_css() ); ?></textarea>
+ <div class="clear"></div>
+ </div>
+ </div>
+ <div id="postbox-container-1" class="postbox-container">
+ <?php do_meta_boxes( 'editcss', 'side', $safecss_post ); ?>
+ </div>
+ </div>
+ <br class="clear" />
+ </div>
+ </form>
+ </div>
+ <?php
+ }
+
+ /**
+ * Content width setting callback
+ */
+ static function content_width_settings() {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+
+ $custom_content_width = get_post_meta( $safecss_post['ID'], 'content_width', true );
+
+ // If custom content width hasn't been overridden and the theme has a content_width value, use that as a default.
+ if ( $custom_content_width <= 0 && ! empty( $GLOBALS['content_width'] ) )
+ $custom_content_width = $GLOBALS['content_width'];
+
+ if ( ! $custom_content_width || ( isset( $GLOBALS['content_width'] ) && $custom_content_width == $GLOBALS['content_width'] ) )
+ $custom_content_width = '';
+
+ ?>
+ <div class="misc-pub-section">
+ <label><?php esc_html_e( 'Media Width:', 'jetpack' ); ?></label>
+ <span id="content-width-display" data-default-text="<?php esc_attr_e( 'Default', 'jetpack' ); ?>" data-custom-text="<?php esc_attr_e( '%s px', 'jetpack' ); ?>"><?php echo $custom_content_width ? sprintf( esc_html__( '%s px', 'jetpack' ), $custom_content_width ) : esc_html_e( 'Default', 'jetpack' ); ?></span>
+ <a class="edit-content-width hide-if-no-js" href="#content-width"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
+ <div id="content-width-select" class="hide-if-js">
+ <input type="hidden" name="custom_content_width" id="custom_content_width" value="<?php echo esc_attr( $custom_content_width ); ?>" />
+ <p>
+ <?php
+
+ printf( /* translators: %1$s is replaced with an input field for numbers. */
+ __( 'Limit width to %1$s pixels for full size images. (<a href="%2$s" rel="noopener noreferrer" target="_blank">More info</a>.)', 'jetpack' ),
+ '<input type="text" id="custom_content_width_visible" value="' . esc_attr( $custom_content_width ) . '" size="4" />',
+ /**
+ * Filter the Custom CSS limited width's support doc URL.
+ *
+ * @module custom-css
+ *
+ * @since 2.2.3
+ *
+ * @param string $url Custom CSS limited width's support doc URL.
+ */
+ apply_filters( 'safecss_limit_width_link', 'http://jetpack.com/support/custom-css/#limited-width' )
+ );
+
+ ?>
+ </p>
+ <?php
+
+ if (
+ ! empty( $GLOBALS['content_width'] )
+ && $custom_content_width != $GLOBALS['content_width']
+ ) {
+ $current_theme = wp_get_theme()->Name;
+
+ ?>
+ <p><?php printf( _n( 'The default content width for the %s theme is %d pixel.', 'The default content width for the %s theme is %d pixels.', intval( $GLOBALS['content_width'] ), 'jetpack' ), $current_theme, intval( $GLOBALS['content_width'] ) ); ?></p>
+ <?php
+ }
+
+ ?>
+ <a class="save-content-width hide-if-no-js button" href="#content-width"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
+ <a class="cancel-content-width hide-if-no-js" href="#content-width"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
+ </div>
+ <script type="text/javascript">
+ jQuery( function ( $ ) {
+ var defaultContentWidth = <?php echo isset( $GLOBALS['content_width'] ) ? json_encode( intval( $GLOBALS['content_width'] ) ) : 0; ?>;
+
+ $( '.edit-content-width' ).bind( 'click', function ( e ) {
+ e.preventDefault();
+
+ $( '#content-width-select' ).slideDown();
+ $( this ).hide();
+ } );
+
+ $( '.cancel-content-width' ).bind( 'click', function ( e ) {
+ e.preventDefault();
+
+ $( '#content-width-select' ).slideUp( function () {
+ $( '.edit-content-width' ).show();
+ $( '#custom_content_width_visible' ).val( $( '#custom_content_width' ).val() );
+ } );
+ } );
+
+ $( '.save-content-width' ).bind( 'click', function ( e ) {
+ e.preventDefault();
+
+ $( '#content-width-select' ).slideUp();
+
+ var newContentWidth = parseInt( $( '#custom_content_width_visible' ).val(), 10 );
+
+ if ( newContentWidth && newContentWidth != defaultContentWidth ) {
+ $( '#content-width-display' ).text(
+ $( '#content-width-display' )
+ .data( 'custom-text' )
+ .replace( '%s', $( '#custom_content_width_visible' ).val() )
+ );
+ }
+ else {
+ $( '#content-width-display' ).text( $( '#content-width-display' ).data( 'default-text' ) );
+ }
+
+ $( '#custom_content_width' ).val( $( '#custom_content_width_visible' ).val() );
+ $( '.edit-content-width' ).show();
+ } );
+ } );
+ </script>
+ </div>
+ <?php
+ }
+
+ static function publish_box() {
+ ?>
+ <div id="minor-publishing">
+ <div id="misc-publishing-actions">
+ <?php
+
+ /**
+ * Filter the array of available Custom CSS preprocessors.
+ *
+ * @module custom-css
+ *
+ * @since 2.0.3
+ *
+ * @param array array() Empty by default.
+ */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+
+ if ( ! empty( $preprocessors ) ) {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+ $selected_preprocessor_key = get_post_meta( $safecss_post['ID'], 'custom_css_preprocessor', true );
+ $selected_preprocessor = isset( $preprocessors[$selected_preprocessor_key] ) ? $preprocessors[$selected_preprocessor_key] : null;
+
+ ?>
+ <div class="misc-pub-section">
+ <label><?php esc_html_e( 'Preprocessor:', 'jetpack' ); ?></label>
+ <span id="preprocessor-display"><?php echo esc_html( $selected_preprocessor ? $selected_preprocessor['name'] : __( 'None', 'jetpack' ) ); ?></span>
+ <a class="edit-preprocessor hide-if-no-js" href="#preprocessor"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
+ <div id="preprocessor-select" class="hide-if-js">
+ <input type="hidden" name="custom_css_preprocessor" id="custom_css_preprocessor" value="<?php echo esc_attr( $selected_preprocessor_key ); ?>" />
+ <select id="preprocessor_choices">
+ <option value=""><?php esc_html_e( 'None', 'jetpack' ); ?></option>
+ <?php
+
+ foreach ( $preprocessors as $preprocessor_key => $preprocessor ) {
+ ?>
+ <option value="<?php echo esc_attr( $preprocessor_key ); ?>" <?php selected( $selected_preprocessor_key, $preprocessor_key ); ?>><?php echo esc_html( $preprocessor['name'] ); ?></option>
+ <?php
+ }
+
+ ?>
+ </select>
+ <a class="save-preprocessor hide-if-no-js button" href="#preprocessor"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
+ <a class="cancel-preprocessor hide-if-no-js" href="#preprocessor"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
+ </div>
+ </div>
+ <?php
+ }
+
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+
+ $add_css = ( get_post_meta( $safecss_post['ID'], 'custom_css_add', true ) != 'no' );
+
+ ?>
+ <div class="misc-pub-section">
+ <label><?php esc_html_e( 'Mode:', 'jetpack' ); ?></label>
+ <span id="css-mode-display"><?php echo esc_html( $add_css ? __( 'Add-on', 'jetpack' ) : __( 'Replacement', 'jetpack' ) ); ?></span>
+ <a class="edit-css-mode hide-if-no-js" href="#css-mode"><?php echo esc_html_e( 'Edit', 'jetpack' ); ?></a>
+ <div id="css-mode-select" class="hide-if-js">
+ <input type="hidden" name="add_to_existing" id="add_to_existing" value="<?php echo $add_css ? 'true' : 'false'; ?>" />
+ <p>
+ <label>
+ <input type="radio" name="add_to_existing_display" value="true" <?php checked( $add_css ); ?>/>
+ <?php _e( 'Add-on CSS <b>(Recommended)</b>', 'jetpack' ); ?>
+ </label>
+ <br />
+ <label>
+ <input type="radio" name="add_to_existing_display" value="false" <?php checked( ! $add_css ); ?>/>
+ <?php printf(
+ __( 'Replace <a href="%s">theme\'s CSS</a> <b>(Advanced)</b>', 'jetpack' ),
+ /**
+ * Filter the theme's stylesheet URL.
+ *
+ * @module custom-css
+ *
+ * @since 1.7.0
+ *
+ * @param string $url Active theme's stylesheet URL. Default to get_stylesheet_uri().
+ */
+ apply_filters( 'safecss_theme_stylesheet_url', get_stylesheet_uri() )
+ ); ?>
+ </label>
+ </p>
+ <a class="save-css-mode hide-if-no-js button" href="#css-mode"><?php esc_html_e( 'OK', 'jetpack' ); ?></a>
+ <a class="cancel-css-mode hide-if-no-js" href="#css-mode"><?php esc_html_e( 'Cancel', 'jetpack' ); ?></a>
+ </div>
+ </div>
+ <?php
+
+ /**
+ * Allows addition of elements to the submit box for custom css on the wp-admin side.
+ *
+ * @module custom-css
+ *
+ * @since 2.0.3
+ */
+ do_action( 'custom_css_submitbox_misc_actions' );
+
+ ?>
+ </div>
+ </div>
+ <div id="major-publishing-actions">
+ <input type="button" class="button" id="preview" name="preview" value="<?php esc_attr_e( 'Preview', 'jetpack' ) ?>" />
+ <div id="publishing-action">
+ <input type="submit" class="button-primary" id="save" name="save" value="<?php ( Jetpack_Custom_CSS::is_freetrial() ) ? esc_attr_e( 'Save &amp; Buy Upgrade', 'jetpack' ) : esc_attr_e( 'Save Stylesheet', 'jetpack' ); ?>" />
+ </div>
+ </div>
+ <?php
+ }
+
+ /**
+ * Render metabox listing CSS revisions and the themes that correspond to the revisions.
+ * Called by safecss_admin
+ *
+ * @global $post
+ * @param array $safecss_post
+ * @uses wp_revisions_to_keep
+ * @uses WP_Query
+ * @uses wp_post_revision_title
+ * @uses esc_html
+ * @uses add_query_arg
+ * @uses menu_page_url
+ * @uses wp_reset_query
+ * @return string
+ */
+ static function revisions_meta_box( $safecss_post ) {
+
+ $show_all_revisions = isset( $_GET['show_all_rev'] );
+
+ if ( function_exists( 'wp_revisions_to_keep' ) ) {
+ $max_revisions = wp_revisions_to_keep( (object) $safecss_post );
+ } else {
+ $max_revisions = defined( 'WP_POST_REVISIONS' ) && is_numeric( WP_POST_REVISIONS ) ? (int) WP_POST_REVISIONS : 25;
+ }
+
+ $posts_per_page = $show_all_revisions ? $max_revisions : 6;
+
+ $revisions = new WP_Query( array(
+ 'posts_per_page' => $posts_per_page,
+ 'post_type' => 'revision',
+ 'post_status' => 'inherit',
+ 'post_parent' => $safecss_post['ID'],
+ 'orderby' => 'date',
+ 'order' => 'DESC'
+ ) );
+
+ if ( $revisions->have_posts() ) { ?>
+ <ul class="post-revisions"><?php
+
+ global $post;
+
+ while ( $revisions->have_posts() ) :
+ $revisions->the_post();
+
+ ?><li>
+ <?php
+ echo wp_post_revision_title( $post );
+
+ if ( ! empty( $post->post_excerpt ) )
+ echo ' (' . esc_html( $post->post_excerpt ) . ')';
+ ?>
+ </li><?php
+
+ endwhile;
+
+ ?></ul><?php
+
+ if ( $revisions->found_posts > 6 && !$show_all_revisions ) {
+ ?>
+ <br>
+ <a href="<?php echo add_query_arg( 'show_all_rev', 'true', menu_page_url( 'editcss', false ) ); ?>"><?php esc_html_e( 'Show all', 'jetpack' ); ?></a>
+ <?php
+ }
+ }
+
+ wp_reset_query();
+ }
+
+ /**
+ * Hook in init at priority 11 to disable custom CSS.
+ */
+ static function disable() {
+ remove_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 );
+ remove_filter( 'stylesheet_uri', array( 'Jetpack_Custom_CSS', 'style_filter' ) );
+ }
+
+ /**
+ * Reset all aspects of Custom CSS on a theme switch so that changing
+ * themes is a sure-fire way to get a clean start.
+ */
+ static function reset() {
+ $safecss_post_id = Jetpack_Custom_CSS::save_revision( '' );
+ $safecss_revision = Jetpack_Custom_CSS::get_current_revision();
+
+ update_option( 'safecss_rev', intval( get_option( 'safecss_rev' ) ) + 1 );
+
+ update_post_meta( $safecss_post_id, 'custom_css_add', 'yes' );
+ update_post_meta( $safecss_post_id, 'content_width', false );
+ update_post_meta( $safecss_post_id, 'custom_css_preprocessor', '' );
+
+ delete_option( 'safecss_add' );
+ delete_option( 'safecss_content_width' );
+
+ update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', 'yes' );
+ update_metadata( 'post', $safecss_revision['ID'], 'content_width', false );
+ update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', '' );
+
+ delete_option( 'safecss_preview_add' );
+ }
+
+ static function is_customizer_preview() {
+ if ( isset ( $GLOBALS['wp_customize'] ) )
+ return ! $GLOBALS['wp_customize']->is_theme_active();
+
+ return false;
+ }
+
+ static function minify( $css, $preprocessor = '' ) {
+ if ( ! $css )
+ return '';
+
+ if ( $preprocessor ) {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+
+ if ( isset( $preprocessors[$preprocessor] ) ) {
+ $css = call_user_func( $preprocessors[$preprocessor]['callback'], $css );
+ }
+ }
+
+ safecss_class();
+ $csstidy = new csstidy();
+ $csstidy->optimise = new safecss( $csstidy );
+
+ $csstidy->set_cfg( 'remove_bslash', false );
+ $csstidy->set_cfg( 'compress_colors', true );
+ $csstidy->set_cfg( 'compress_font-weight', true );
+ $csstidy->set_cfg( 'remove_last_;', true );
+ $csstidy->set_cfg( 'case_properties', true );
+ $csstidy->set_cfg( 'discard_invalid_properties', true );
+ $csstidy->set_cfg( 'css_level', 'CSS3.0' );
+ $csstidy->set_cfg( 'template', 'highest');
+ $csstidy->parse( $css );
+
+ return $csstidy->print->plain();
+ }
+
+ /**
+ * When restoring a SafeCSS post revision, also copy over the
+ * content_width and custom_css_add post metadata.
+ */
+ static function restore_revision( $_post_id, $_revision_id ) {
+ $_post = get_post( $_post_id );
+
+ if ( 'safecss' != $_post->post_type )
+ return;
+
+ $safecss_revision = Jetpack_Custom_CSS::get_current_revision();
+
+ $content_width = get_post_meta( $_revision_id, 'content_width', true );
+ $custom_css_add = get_post_meta( $_revision_id, 'custom_css_add', true );
+ $preprocessor = get_post_meta( $_revision_id, 'custom_css_preprocessor', true );
+
+ update_metadata( 'post', $safecss_revision['ID'], 'content_width', $content_width );
+ update_metadata( 'post', $safecss_revision['ID'], 'custom_css_add', $custom_css_add );
+ update_metadata( 'post', $safecss_revision['ID'], 'custom_css_preprocessor', $preprocessor );
+
+ delete_option( 'safecss_add' );
+ delete_option( 'safecss_content_width' );
+
+ update_post_meta( $_post->ID, 'content_width', $content_width );
+ update_post_meta( $_post->ID, 'custom_css_add', $custom_css_add );
+ update_post_meta( $_post->ID, 'custom_css_preprocessor', $preprocessor );
+
+ delete_option( 'safecss_preview_add' );
+ }
+
+ /**
+ * Migration routine for moving safecss from wp_options to wp_posts to support revisions
+ *
+ * @return void
+ */
+ static function upgrade() {
+ $css = get_option( 'safecss' );
+
+ if ( get_option( 'safecss_revision_migrated' ) ) {
+ return false;
+ }
+
+ // Check if CSS is stored in wp_options
+ if ( $css ) {
+ // Remove the async actions from publish_post
+ remove_action( 'publish_post', 'queue_publish_post' );
+
+ $post = array();
+ $post['post_content'] = $css;
+ $post['post_title'] = 'safecss';
+ $post['post_status'] = 'publish';
+ $post['post_type'] = 'safecss';
+
+ // Insert the CSS into wp_posts
+ $post_id = wp_insert_post( $post );
+ // Check for errors
+ if ( !$post_id or is_wp_error( $post_id ) )
+ die( $post_id->get_error_message() );
+
+ // Delete safecss option
+ delete_option( 'safecss' );
+ }
+
+ unset( $css );
+
+ // Check if we have already done this
+ if ( !get_option( 'safecss_revision_migrated' ) ) {
+ define( 'DOING_MIGRATE', true );
+
+ // Get hashes of safecss post and current revision
+ $safecss_post = Jetpack_Custom_CSS::get_post();
+
+ if ( empty( $safecss_post ) )
+ return;
+
+ $safecss_post_hash = md5( $safecss_post['post_content'] );
+ $current_revision = Jetpack_Custom_CSS::get_current_revision();
+
+ if ( null == $current_revision )
+ return;
+
+ $current_revision_hash = md5( $current_revision['post_content'] );
+
+ // If hashes are not equal, set safecss post with content from current revision
+ if ( $safecss_post_hash !== $current_revision_hash ) {
+ Jetpack_Custom_CSS::save_revision( $current_revision['post_content'] );
+ // Reset post_content to display the migrated revsion
+ $safecss_post['post_content'] = $current_revision['post_content'];
+ }
+
+ // Set option so that we dont keep doing this
+ update_option( 'safecss_revision_migrated', time() );
+ }
+
+ $newest_safecss_post = Jetpack_Custom_CSS::get_current_revision();
+
+ if ( $newest_safecss_post ) {
+ if ( get_option( 'safecss_content_width' ) ) {
+ // Add the meta to the post and the latest revision.
+ update_post_meta( $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) );
+ update_metadata( 'post', $newest_safecss_post['ID'], 'content_width', get_option( 'safecss_content_width' ) );
+
+ delete_option( 'safecss_content_width' );
+ }
+
+ if ( get_option( 'safecss_add' ) ) {
+ update_post_meta( $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) );
+ update_metadata( 'post', $newest_safecss_post['ID'], 'custom_css_add', get_option( 'safecss_add' ) );
+
+ delete_option( 'safecss_add' );
+ }
+ }
+ }
+
+ /**
+ * Adds a filter to the redirect location in `wp-admin/revisions.php`.
+ */
+ static function add_revision_redirect() {
+ add_filter( 'wp_redirect', array( __CLASS__, 'revision_redirect' ) );
+ }
+
+ /**
+ * Filters the redirect location in `wp-admin/revisions.php`.
+ *
+ * @param string $location The path to redirect to.
+ * @return string
+ */
+ static function revision_redirect( $location ) {
+ $post = get_post();
+
+ if ( ! empty( $post->post_type ) && 'safecss' == $post->post_type ) {
+ $location = 'themes.php?page=editcss';
+
+ if ( 'edit.php' == $location ) {
+ $location = '';
+ }
+ }
+
+ return $location;
+ }
+
+ static function revision_post_link( $post_link, $post_id, $context ) {
+ if ( !$post_id = (int) $post_id ) {
+ return $post_link;
+ }
+
+ if ( !$post = get_post( $post_id ) ) {
+ return $post_link;
+ }
+
+ if ( 'safecss' != $post->post_type ) {
+ return $post_link;
+ }
+
+ $post_link = admin_url( 'themes.php?page=editcss' );
+
+ if ( 'display' == $context ) {
+ return esc_url( $post_link );
+ }
+
+ return esc_url_raw( $post_link );
+ }
+
+ /**
+ * When on the edit screen, make sure the custom content width
+ * setting is applied to the large image size.
+ */
+ static function editor_max_image_size( $dims, $size = 'medium', $context = null ) {
+ list( $width, $height ) = $dims;
+
+ if ( 'large' == $size && 'edit' == $context )
+ $width = Jetpack::get_content_width();
+
+ return array( $width, $height );
+ }
+
+ /**
+ * Override the content_width with a custom value if one is set.
+ */
+ static function jetpack_content_width( $content_width ) {
+ $custom_content_width = 0;
+
+ if ( Jetpack_Custom_CSS::is_preview() ) {
+ $safecss_post = Jetpack_Custom_CSS::get_current_revision();
+ $custom_content_width = intval( get_post_meta( $safecss_post['ID'], 'content_width', true ) );
+ } else if ( ! Jetpack_Custom_CSS::is_freetrial() ) {
+ $custom_css_post_id = Jetpack_Custom_CSS::post_id();
+ if ( $custom_css_post_id )
+ $custom_content_width = intval( get_post_meta( $custom_css_post_id, 'content_width', true ) );
+ }
+
+ if ( $custom_content_width > 0 )
+ $content_width = $custom_content_width;
+
+ return $content_width;
+ }
+}
+
+class Jetpack_Safe_CSS {
+ static function filter_attr( $css, $element = 'div' ) {
+ safecss_class();
+
+ $css = $element . ' {' . $css . '}';
+
+ $csstidy = new csstidy();
+ $csstidy->optimise = new safecss( $csstidy );
+ $csstidy->set_cfg( 'remove_bslash', false );
+ $csstidy->set_cfg( 'compress_colors', false );
+ $csstidy->set_cfg( 'compress_font-weight', false );
+ $csstidy->set_cfg( 'discard_invalid_properties', true );
+ $csstidy->set_cfg( 'merge_selectors', false );
+ $csstidy->set_cfg( 'remove_last_;', false );
+ $csstidy->set_cfg( 'css_level', 'CSS3.0' );
+
+ $css = preg_replace( '/\\\\([0-9a-fA-F]{4})/', '\\\\\\\\$1', $css );
+ $css = wp_kses_split( $css, array(), array() );
+ $csstidy->parse( $css );
+
+ $css = $csstidy->print->plain();
+
+ $css = str_replace( array( "\n","\r","\t" ), '', $css );
+
+ preg_match( "/^{$element}\s*{(.*)}\s*$/", $css, $matches );
+
+ if ( empty( $matches[1] ) )
+ return '';
+
+ return $matches[1];
+ }
+}
+
+function migrate() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::upgrade()' );
+
+ return Jetpack_Custom_CSS::upgrade();
+}
+
+function safecss_revision_redirect( $redirect ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revision_redirect()' );
+
+ return Jetpack_Custom_CSS::revision_redirect( $redirect );
+}
+
+function safecss_revision_post_link( $post_link, $post_id, $context ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revision_post_link()' );
+
+ return Jetpack_Custom_CSS::revision_post_link( $post_link, $post_id, $context );
+}
+
+function get_safecss_post() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_post()' );
+
+ return Jetpack_Custom_CSS::get_post();
+}
+
+function custom_css_post_id() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::post_id()' );
+
+ return Jetpack_Custom_CSS::post_id();
+}
+
+function get_current_revision() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_current_revision()' );
+
+ return Jetpack_Custom_CSS::get_current_revision();
+}
+
+function save_revision( $css, $is_preview = false, $preprocessor = '' ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::save_revision()' );
+
+ return Jetpack_Custom_CSS::save_revision( $css, $is_preview, $preprocessor );
+}
+
+function safecss_skip_stylesheet() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::skip_stylesheet()' );
+
+ return Jetpack_Custom_CSS::skip_stylesheet();
+}
+
+function safecss_init() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::init()' );
+
+ return Jetpack_Custom_CSS::init();
+}
+
+function safecss_is_preview() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_preview()' );
+
+ return Jetpack_Custom_CSS::is_preview();
+}
+
+function safecss_is_freetrial() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_freetrial()' );
+
+ return Jetpack_Custom_CSS::is_freetrial();
+}
+
+function safecss( $compressed = false ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::get_css()' );
+
+ return Jetpack_Custom_CSS::get_css( $compressed );
+}
+
+function safecss_print() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::print_css()' );
+
+ return Jetpack_Custom_CSS::print_css();
+}
+
+function safecss_style() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::link_tag()' );
+
+ return Jetpack_Custom_CSS::link_tag();
+}
+
+function safecss_style_filter( $current ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::style_filter()' );
+
+ return Jetpack_Custom_CSS::style_filter( $current );
+}
+
+function safecss_buffer( $html ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::buffer()' );
+
+ return Jetpack_Custom_CSS::buffer( $html );
+}
+
+function safecss_preview_links( $matches ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::preview_links()' );
+
+ return Jetpack_Custom_CSS::preview_links( $matches );
+}
+
+function safecss_preview_flag() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::preview_flag()' );
+
+ return Jetpack_Custom_CSS::preview_flag();
+}
+
+function safecss_menu() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::menu()' );
+
+ return Jetpack_Custom_CSS::menu();
+}
+
+function update_title() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::update_title()' );
+
+ return Jetpack_Custom_CSS::update_title();
+}
+
+function safecss_prettify_post_revisions() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::prettify_post_revisions()' );
+
+ return Jetpack_Custom_CSS::prettify_post_revisions();
+}
+
+function safecss_remove_title_excerpt_from_revisions() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::remove_title_excerpt_from_revisions()' );
+
+ return Jetpack_Custom_CSS::remove_title_excerpt_from_revisions();
+}
+
+function safecss_post_title( $title, $post_id ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::post_title()' );
+
+ return Jetpack_Custom_CSS::post_title( $title, $post_id );
+}
+
+function safe_css_enqueue_scripts() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::enqueue_scripts()' );
+
+ return Jetpack_Custom_CSS::enqueue_scripts( null );
+}
+
+function safecss_admin_head() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::admin_head()' );
+
+ return Jetpack_Custom_CSS::admin_head();
+}
+
+function safecss_saved() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::saved_message()' );
+
+ return Jetpack_Custom_CSS::saved_message();
+}
+
+function safecss_admin() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::admin()' );
+
+ return Jetpack_Custom_CSS::admin();
+}
+
+function custom_css_meta_box() {
+ _deprecated_function( __FUNCTION__, '2.1', 'add_meta_box( $id, $title, $callback, \'editcss\', \'side\' )' );
+}
+
+function custom_css_post_revisions_meta_box( $safecss_post ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::revisions_meta_box()' );
+
+ return Jetpack_Custom_CSS::revisions_meta_box( $safecss_post );
+}
+
+function disable_safecss_style() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::disable()' );
+
+ return Jetpack_Custom_CSS::disable();
+}
+
+function custom_css_reset() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::reset()' );
+
+ return Jetpack_Custom_CSS::reset();
+}
+
+function custom_css_is_customizer_preview() {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::is_customizer_preview()' );
+
+ return Jetpack_Custom_CSS::is_customizer_preview();
+}
+
+function custom_css_minify( $css, $preprocessor = '' ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::minify()' );
+
+ return Jetpack_Custom_CSS::minify( $css, $preprocessor );
+}
+
+function custom_css_restore_revision( $_post_id, $_revision_id ) {
+ _deprecated_function( __FUNCTION__, '2.1', 'Jetpack_Custom_CSS::restore_revision()' );
+
+ return Jetpack_Custom_CSS::restore_revision( $_post_id, $_revision_id );
+}
+
+if ( ! function_exists( 'safecss_class' ) ) :
+function safecss_class() {
+ // Wrapped so we don't need the parent class just to load the plugin
+ if ( class_exists('safecss') )
+ return;
+
+ require_once( dirname( __FILE__ ) . '/csstidy/class.csstidy.php' );
+
+ class safecss extends csstidy_optimise {
+
+ function postparse() {
+
+ /**
+ * Fires after parsing the css.
+ *
+ * @module custom-css
+ *
+ * @since 1.8.0
+ *
+ * @param obj $this CSSTidy object.
+ */
+ do_action( 'csstidy_optimize_postparse', $this );
+
+ return parent::postparse();
+ }
+
+ function subvalue() {
+
+ /**
+ * Fires before optimizing the Custom CSS subvalue.
+ *
+ * @module custom-css
+ *
+ * @since 1.8.0
+ *
+ * @param obj $this CSSTidy object.
+ **/
+ do_action( 'csstidy_optimize_subvalue', $this );
+
+ return parent::subvalue();
+ }
+ }
+}
+endif;
+
+if ( ! function_exists( 'safecss_filter_attr' ) ) {
+ function safecss_filter_attr( $css, $element = 'div' ) {
+ return Jetpack_Safe_CSS::filter_attr( $css, $element );
+ }
+}
+
+include_once dirname( __FILE__ ) . '/custom-css/preprocessors.php';
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/blank.css b/plugins/jetpack/modules/custom-css/custom-css/css/blank.css
new file mode 100644
index 00000000..c84ecefc
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/blank.css
@@ -0,0 +1 @@
+/* */ \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.css b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.css
new file mode 100644
index 00000000..359717bf
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.css
@@ -0,0 +1,262 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+/* NOAUTORTL */
+.rtl .CodeMirror {
+ direction: rtl; /* code should always be written left to right */
+}
+/* BASICS */
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 400px;
+}
+.CodeMirror-scroll {
+ /* Set scrolling behavior here */
+ overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-left: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 5px 0 3px;
+ min-width: 20px;
+ text-align: left;
+ color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-right: 1px solid black;
+ z-index: 3;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-right: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+ z-index: 1;
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+.cm-tab { display: inline-block; }
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ line-height: 1;
+ position: relative;
+ overflow: hidden;
+ background: white;
+ color: black;
+}
+
+.CodeMirror-scroll {
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-left: -30px;
+ padding-bottom: 30px; padding-left: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ left: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; right: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ left: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ right: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; right: 0; top: 0;
+ padding-bottom: 30px;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ padding-bottom: 30px;
+ margin-bottom: -32px;
+ display: inline-block;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */ border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+.CodeMirror-code pre {
+ border-left: 30px solid transparent;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+}
+.CodeMirror-wrap .CodeMirror-code pre {
+ border-left: none;
+ width: auto;
+}
+.CodeMirror-linebackground {
+ position: absolute;
+ right: 0; left: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {
+}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%; height: 0px;
+ overflow: hidden;
+ visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ visibility: hidden;
+ border-left: none;
+ width: 0;
+}
+.CodeMirror-focused div.CodeMirror-cursor {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursor {
+ visibility: hidden;
+ }
+}
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.min.css
new file mode 100644
index 00000000..bb4ede28
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror-rtl.min.css
@@ -0,0 +1 @@
+.rtl .CodeMirror{direction:rtl}.CodeMirror{font-family:monospace;height:400px}.CodeMirror-scroll{overflow:auto}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-left:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 5px 0 3px;min-width:20px;text-align:left;color:#999}.CodeMirror div.CodeMirror-cursor{border-right:1px solid #000;z-index:3}.CodeMirror div.CodeMirror-secondarycursor{border-right:1px solid silver}.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor{width:auto;border:0;background:#7e7;z-index:1}.cm-tab{display:inline-block}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable{color:#000}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-property{color:#000}.cm-s-default .cm-operator{color:#000}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-error{color:red}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-invalidchar{color:red}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{line-height:1;position:relative;overflow:hidden;background:#fff;color:#000}.CodeMirror-scroll{margin-bottom:-30px;margin-left:-30px;padding-bottom:30px;padding-left:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{left:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;right:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{left:0;bottom:0}.CodeMirror-gutter-filler{right:0;bottom:0}.CodeMirror-gutters{position:absolute;right:0;top:0;padding-bottom:30px;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;padding-bottom:30px;margin-bottom:-32px;display:inline-block}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text}.CodeMirror pre{border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-code pre{border-left:30px solid transparent;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.CodeMirror-wrap .CodeMirror-code pre{border-left:none;width:auto}.CodeMirror-linebackground{position:absolute;right:0;left:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-wrap .CodeMirror-scroll{overflow-x:hidden}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;visibility:hidden;border-left:none;width:0}.CodeMirror-focused div.CodeMirror-cursor{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}@media print{.CodeMirror div.CodeMirror-cursor{visibility:hidden}} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.css b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.css
new file mode 100644
index 00000000..db90a5a5
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.css
@@ -0,0 +1,262 @@
+/* NOAUTORTL */
+.rtl .CodeMirror {
+ direction: ltr; /* code should always be written left to right */
+}
+/* BASICS */
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 400px;
+}
+.CodeMirror-scroll {
+ /* Set scrolling behavior here */
+ overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-left: 1px solid black;
+ z-index: 3;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+ z-index: 1;
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+.cm-tab { display: inline-block; }
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ line-height: 1;
+ position: relative;
+ overflow: hidden;
+ background: white;
+ color: black;
+}
+
+.CodeMirror-scroll {
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px; padding-right: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ padding-bottom: 30px;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ padding-bottom: 30px;
+ margin-bottom: -32px;
+ display: inline-block;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+.CodeMirror-code pre {
+ border-right: 30px solid transparent;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+}
+.CodeMirror-wrap .CodeMirror-code pre {
+ border-right: none;
+ width: auto;
+}
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {
+}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%; height: 0px;
+ overflow: hidden;
+ visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ visibility: hidden;
+ border-right: none;
+ width: 0;
+}
+.CodeMirror-focused div.CodeMirror-cursor {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursor {
+ visibility: hidden;
+ }
+}
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.min.css
new file mode 100644
index 00000000..92af420a
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/codemirror.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+.rtl .CodeMirror{direction:ltr}.CodeMirror{font-family:monospace;height:400px}.CodeMirror-scroll{overflow:auto}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999}.CodeMirror div.CodeMirror-cursor{border-left:1px solid #000;z-index:3}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor{width:auto;border:0;background:#7e7;z-index:1}.cm-tab{display:inline-block}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable{color:#000}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-property{color:#000}.cm-s-default .cm-operator{color:#000}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-error{color:red}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-invalidchar{color:red}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{line-height:1;position:relative;overflow:hidden;background:#fff;color:#000}.CodeMirror-scroll{margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;padding-right:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;padding-bottom:30px;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;padding-bottom:30px;margin-bottom:-32px;display:inline-block}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text}.CodeMirror pre{border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-code pre{border-right:30px solid transparent;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.CodeMirror-wrap .CodeMirror-code pre{border-right:none;width:auto}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-wrap .CodeMirror-scroll{overflow-x:hidden}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;visibility:hidden;border-right:none;width:0}.CodeMirror-focused div.CodeMirror-cursor{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}@media print{.CodeMirror div.CodeMirror-cursor{visibility:hidden}} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.css b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.css
new file mode 100644
index 00000000..a8202fb2
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.css
@@ -0,0 +1,33 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+#revision-field-post_title, #revision-field-post_excerpt {
+ display: none;
+}
+
+#safecssform {
+ position: relative;
+}
+
+#poststuff {
+ padding-top: 0;
+}
+
+#safecss {
+ min-height: 250px;
+ width: 100%;
+}
+
+.misc-pub-section > span {
+ font-weight: bold;
+}
+
+.misc-pub-section > div {
+ margin-top: 3px;
+}
+#safecss-ace .ace_gutter {
+ z-index: 1;
+}
+
+#post-body-content{
+ margin-bottom: 20px;
+}
+
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.min.css
new file mode 100644
index 00000000..62eb4809
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor-rtl.min.css
@@ -0,0 +1 @@
+#revision-field-post_excerpt,#revision-field-post_title{display:none}#safecssform{position:relative}#poststuff{padding-top:0}#safecss{min-height:250px;width:100%}.misc-pub-section>span{font-weight:700}.misc-pub-section>div{margin-top:3px}#safecss-ace .ace_gutter{z-index:1}#post-body-content{margin-bottom:20px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.css b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.css
new file mode 100644
index 00000000..f85bec14
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.css
@@ -0,0 +1,32 @@
+#revision-field-post_title, #revision-field-post_excerpt {
+ display: none;
+}
+
+#safecssform {
+ position: relative;
+}
+
+#poststuff {
+ padding-top: 0;
+}
+
+#safecss {
+ min-height: 250px;
+ width: 100%;
+}
+
+.misc-pub-section > span {
+ font-weight: bold;
+}
+
+.misc-pub-section > div {
+ margin-top: 3px;
+}
+#safecss-ace .ace_gutter {
+ z-index: 1;
+}
+
+#post-body-content{
+ margin-bottom: 20px;
+}
+
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.min.css
new file mode 100644
index 00000000..6245c019
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/css-editor.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+#revision-field-post_excerpt,#revision-field-post_title{display:none}#safecssform{position:relative}#poststuff{padding-top:0}#safecss{min-height:250px;width:100%}.misc-pub-section>span{font-weight:700}.misc-pub-section>div{margin-top:3px}#safecss-ace .ace_gutter{z-index:1}#post-body-content{margin-bottom:20px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/customizer-control.css b/plugins/jetpack/modules/custom-css/custom-css/css/customizer-control.css
new file mode 100644
index 00000000..e7927ff2
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/customizer-control.css
@@ -0,0 +1,150 @@
+/* NOAUTORTL */
+.for-codemirror, .CodeMirror {
+ font-family: Consolas, Monaco, monospace;
+ font-size: 12px;
+ line-height: 16px;
+ margin: 0;
+ direction: ltr;
+ text-align: left;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+#customize-control-custom_css#customize-control-custom_css {
+ margin-right: -12px;
+ margin-left: -12px;
+ width: calc( 100% + 24px );
+}
+.customize-control-code_editor .CodeMirror {
+ height: 300px;
+ height: calc( 100vh - 268px );
+}
+.for-codemirror {
+ width: 98%;
+ height: 300px;
+}
+#customize-control-wpcom_custom_css_content_width_control {
+ position: relative;
+}
+#customize-control-wpcom_custom_css_content_width_control > label {
+ position: relative;
+ width: 100px;
+}
+#customize-control-wpcom_custom_css_content_width_control .customize-control-title {
+ padding-bottom: 6px;
+}
+#customize-controls #customize-control-wpcom_custom_css_content_width_control input[type="text"], /* stronger selector to override new-customizer.css */
+#customize-control-wpcom_custom_css_content_width_control input[type="text"] {
+ width: 64px;
+ padding-right: 22px;
+ text-align: right;
+}
+#customize-control-wpcom_custom_css_content_width_control input[type="text"] + span {
+ position: absolute;
+ left: 43px;
+ padding-top: 3px;
+ opacity: .8;
+}
+@-moz-document url-prefix() {
+ #customize-control-wpcom_custom_css_content_width_control input[type="text"] + span {
+ top: 47px;
+ }
+}
+#customize-control-wpcom_custom_css_content_width_control input[type="text"]:focus + span {
+ opacity: 1;
+}
+
+#customize-control-wpcom_custom_css_content_width_control .description {
+ display: block;
+ margin: 28px 0 0 0;
+ color: #aaa;
+}
+
+#customize-control-wpcom_custom_css_content_width_control .description strong {
+ font-style: normal;
+}
+
+#customize-control-jetpack_custom_css_control {
+ position: relative;
+}
+
+.css-help {
+ border-bottom: 1px solid #ddd;
+ background: #ffffff;
+ position: relative;
+ right: 0;
+ left: 0;
+ width: 100%;
+ padding: 0;
+ overflow: hidden;
+}
+
+.css-help a {
+ float: none;
+ display: inline-block;
+ text-decoration: none;
+ border-bottom: 4px solid transparent;
+ color: #555d66;
+ padding: 7px 10px 5px;
+ transition: .15s color ease-in-out,.15s background-color ease-in-out,.15s border-color ease-in-out;
+}
+
+.css-help a:hover {
+ color: #0073aa;
+ background-color: #f3f3f5;
+}
+
+.css-help a:before {
+ display: inline-block;
+ position: relative;
+ font-family: dashicons;
+ font-size: 20px;
+ padding-right: 3px;
+ top: 5px;
+ line-height: 1px;
+}
+
+.css-help a:focus {
+ color: #0073aa;
+ background-color: #f3f3f5;
+ border-bottom-color: #0073aa;
+ box-shadow: none;
+}
+
+.css-help a#revisions-link:before {
+ content: "\f321";
+}
+
+.css-help a#help-link:before {
+ content: "\f223";
+}
+
+#sub-accordion-section-custom_css .customize-control {
+ margin: 12px 0;
+}
+
+#sub-accordion-section-custom_css .customize-control-jetpackCss {
+ margin: 0 -12px;
+ width: calc( 100% + 24px );
+}
+
+#customize-theme-controls #sub-accordion-section-custom_css .customize-control-title {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#sub-accordion-section-custom_css #customize-control-jetpack_css_preprocessors_control select {
+ max-width: 75%;
+}
+
+body.editing-css .wp-full-overlay-sidebar {
+ width: 500px;
+}
+
+body.editing-css .wp-full-overlay.expanded {
+ margin-left: 500px;
+}
+
+input[type=jetpackCss] {
+ display: none;
+}
+
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/rtl/codemirror-rtl.css b/plugins/jetpack/modules/custom-css/custom-css/css/rtl/codemirror-rtl.css
new file mode 100644
index 00000000..74ad41fb
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/rtl/codemirror-rtl.css
@@ -0,0 +1,260 @@
+/* This file was automatically generated on Sep 10 2013 23:18:59 */
+
+/* BASICS */
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+}
+.CodeMirror-scroll {
+ /* Set scrolling behavior here */
+ overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-left: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 5px 0 3px;
+ min-width: 20px;
+ text-align: left;
+ color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+ border-right: 1px solid black;
+ z-index: 3;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-right: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+ z-index: 1;
+}
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+
+.cm-tab { display: inline-block; }
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable {color: black;}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-property {color: black;}
+.cm-s-default .cm-operator {color: black;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-error {color: #f00;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-invalidchar {color: #f00;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ line-height: 1;
+ position: relative;
+ overflow: hidden;
+ background: white;
+ color: black;
+}
+
+.CodeMirror-scroll {
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-left: -30px;
+ padding-bottom: 30px; padding-left: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actuall scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ left: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; right: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ left: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ right: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; right: 0; top: 0;
+ padding-bottom: 30px;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ padding-bottom: 30px;
+ margin-bottom: -32px;
+ display: inline-block;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+.CodeMirror-code pre {
+ border-left: 30px solid transparent;
+ width: -webkit-fit-content;
+ width: -moz-fit-content;
+ width: fit-content;
+}
+.CodeMirror-wrap .CodeMirror-code pre {
+ border-left: none;
+ width: auto;
+}
+.CodeMirror-linebackground {
+ position: absolute;
+ right: 0; left: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {
+}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%; height: 0px;
+ overflow: hidden;
+ visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+ position: absolute;
+ visibility: hidden;
+ border-left: none;
+ width: 0;
+}
+.CodeMirror-focused div.CodeMirror-cursor {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursor {
+ visibility: hidden;
+ }
+}
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.css b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.css
new file mode 100644
index 00000000..649fb168
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.css
@@ -0,0 +1,7 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+.CodeMirror, #safecss {
+ font-family: Consolas, Monaco, monospace;
+ font-size: 12px;
+ line-height: 16px;
+ min-height: 300px;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.min.css
new file mode 100644
index 00000000..8dce7dda
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror-rtl.min.css
@@ -0,0 +1 @@
+#safecss,.CodeMirror{font-family:Consolas,Monaco,monospace;font-size:12px;line-height:16px;min-height:300px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.css b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.css
new file mode 100644
index 00000000..924bb4d0
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.css
@@ -0,0 +1,6 @@
+.CodeMirror, #safecss {
+ font-family: Consolas, Monaco, monospace;
+ font-size: 12px;
+ line-height: 16px;
+ min-height: 300px;
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.min.css b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.min.css
new file mode 100644
index 00000000..024ae478
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/css/use-codemirror.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+#safecss,.CodeMirror{font-family:Consolas,Monaco,monospace;font-size:12px;line-height:16px;min-height:300px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/codemirror.min.js b/plugins/jetpack/modules/custom-css/custom-css/js/codemirror.min.js
new file mode 100644
index 00000000..efc8a0fb
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/codemirror.min.js
@@ -0,0 +1,11 @@
+/**
+ * http://codemirror.net/
+ * MIT License
+ * Includes CSS & LESS modes.
+ * v3.19.1
+ */
+window.CodeMirror=function(){"use strict";function x(a,c){if(!(this instanceof x))return new x(a,c);this.options=c=c||{};for(var d in _c)!c.hasOwnProperty(d)&&_c.hasOwnProperty(d)&&(c[d]=_c[d]);J(c);var e="string"==typeof c.value?0:c.value.first,f=this.display=y(a,e);f.wrapper.CodeMirror=this,G(this),c.autofocus&&!p&&Nb(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,focused:!1,suppressEdits:!1,pasteIncoming:!1,draggingText:!1,highlight:new Xe},E(this),c.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap");var g=c.value;"string"==typeof g&&(g=new ge(c.value,c.mode)),Fb(this,ke)(this,g),b&&setTimeout(ff(Mb,this,!0),20),Pb(this);var h;try{h=document.activeElement==f.input}catch(i){}h||c.autofocus&&!p?setTimeout(ff(mc,this),20):nc(this),Fb(this,function(){for(var a in $c)$c.propertyIsEnumerable(a)&&$c[a](this,c[a],bd);for(var b=0;b<fd.length;++b)fd[b](this)})()}function y(a,b){var d={},e=d.input=lf("textarea",null,null,"position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");return f?e.style.width="1000px":e.setAttribute("wrap","off"),o&&(e.style.border="1px solid black"),e.setAttribute("autocorrect","off"),e.setAttribute("autocapitalize","off"),e.setAttribute("spellcheck","false"),d.inputDiv=lf("div",[e],null,"overflow: hidden; position: relative; width: 3px; height: 0px;"),d.scrollbarH=lf("div",[lf("div",null,null,"height: 1px")],"CodeMirror-hscrollbar"),d.scrollbarV=lf("div",[lf("div",null,null,"width: 1px")],"CodeMirror-vscrollbar"),d.scrollbarFiller=lf("div",null,"CodeMirror-scrollbar-filler"),d.gutterFiller=lf("div",null,"CodeMirror-gutter-filler"),d.lineDiv=lf("div",null,"CodeMirror-code"),d.selectionDiv=lf("div",null,null,"position: relative; z-index: 1"),d.cursor=lf("div","\xa0","CodeMirror-cursor"),d.otherCursor=lf("div","\xa0","CodeMirror-cursor CodeMirror-secondarycursor"),d.measure=lf("div",null,"CodeMirror-measure"),d.lineSpace=lf("div",[d.measure,d.selectionDiv,d.lineDiv,d.cursor,d.otherCursor],null,"position: relative; outline: none"),d.mover=lf("div",[lf("div",[d.lineSpace],"CodeMirror-lines")],null,"position: relative"),d.sizer=lf("div",[d.mover],"CodeMirror-sizer"),d.heightForcer=lf("div",null,null,"position: absolute; height: "+Ve+"px; width: 1px;"),d.gutters=lf("div",null,"CodeMirror-gutters"),d.lineGutter=null,d.scroller=lf("div",[d.sizer,d.heightForcer,d.gutters],"CodeMirror-scroll"),d.scroller.setAttribute("tabIndex","-1"),d.wrapper=lf("div",[d.inputDiv,d.scrollbarH,d.scrollbarV,d.scrollbarFiller,d.gutterFiller,d.scroller],"CodeMirror"),c&&(d.gutters.style.zIndex=-1,d.scroller.style.paddingRight=0),a.appendChild?a.appendChild(d.wrapper):a(d.wrapper),o&&(e.style.width="0px"),f||(d.scroller.draggable=!0),k?(d.inputDiv.style.height="1px",d.inputDiv.style.position="absolute"):c&&(d.scrollbarH.style.minWidth=d.scrollbarV.style.minWidth="18px"),d.viewOffset=d.lastSizeC=0,d.showingFrom=d.showingTo=b,d.lineNumWidth=d.lineNumInnerWidth=d.lineNumChars=null,d.prevInput="",d.alignWidgets=!1,d.pollingFast=!1,d.poll=new Xe,d.cachedCharWidth=d.cachedTextHeight=null,d.measureLineCache=[],d.measureLineCachePos=0,d.inaccurateSelection=!1,d.maxLine=null,d.maxLineLength=0,d.maxLineChanged=!1,d.wheelDX=d.wheelDY=d.wheelStartX=d.wheelStartY=null,d}function z(a){a.doc.mode=x.getMode(a.options,a.doc.modeOption),a.doc.iter(function(a){a.stateAfter&&(a.stateAfter=null),a.styles&&(a.styles=null)}),a.doc.frontier=a.doc.first,bb(a,100),a.state.modeGen++,a.curOp&&Ib(a)}function A(a){a.options.lineWrapping?(a.display.wrapper.className+=" CodeMirror-wrap",a.display.sizer.style.minWidth=""):(a.display.wrapper.className=a.display.wrapper.className.replace(" CodeMirror-wrap",""),I(a)),C(a),Ib(a),pb(a),setTimeout(function(){K(a)},100)}function B(a){var b=Ab(a.display),c=a.options.lineWrapping,d=c&&Math.max(5,a.display.scroller.clientWidth/Bb(a.display)-3);return function(e){return Gd(a.doc,e)?0:c?(Math.ceil(e.text.length/d)||1)*b:b}}function C(a){var b=a.doc,c=B(a);b.iter(function(a){var b=c(a);b!=a.height&&oe(a,b)})}function D(a){var b=kd[a.options.keyMap],c=b.style;a.display.wrapper.className=a.display.wrapper.className.replace(/\s*cm-keymap-\S+/g,"")+(c?" cm-keymap-"+c:""),a.state.disableInput=b.disableInput}function E(a){a.display.wrapper.className=a.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+a.options.theme.replace(/(^|\s)\s*/g," cm-s-"),pb(a)}function F(a){G(a),Ib(a),setTimeout(function(){M(a)},20)}function G(a){var b=a.display.gutters,c=a.options.gutters;mf(b);for(var d=0;d<c.length;++d){var e=c[d],f=b.appendChild(lf("div",null,"CodeMirror-gutter "+e));"CodeMirror-linenumbers"==e&&(a.display.lineGutter=f,f.style.width=(a.display.lineNumWidth||1)+"px")}b.style.display=d?"":"none"}function H(a,b){if(0==b.height)return 0;for(var d,c=b.text.length,e=b;d=Dd(e);){var f=d.find();e=le(a,f.from.line),c+=f.from.ch-f.to.ch}for(e=b;d=Ed(e);){var f=d.find();c-=e.text.length-f.from.ch,e=le(a,f.to.line),c+=e.text.length-f.to.ch}return c}function I(a){var b=a.display,c=a.doc;b.maxLine=le(c,c.first),b.maxLineLength=H(c,b.maxLine),b.maxLineChanged=!0,c.iter(function(a){var d=H(c,a);d>b.maxLineLength&&(b.maxLineLength=d,b.maxLine=a)})}function J(a){var b=bf(a.gutters,"CodeMirror-linenumbers");-1==b&&a.lineNumbers?a.gutters=a.gutters.concat(["CodeMirror-linenumbers"]):b>-1&&!a.lineNumbers&&(a.gutters=a.gutters.slice(0),a.gutters.splice(b,1))}function K(a){var b=a.display,c=a.doc.height,d=c+gb(b);b.sizer.style.minHeight=b.heightForcer.style.top=d+"px",b.gutters.style.height=Math.max(d,b.scroller.clientHeight-Ve)+"px";var e=Math.max(d,b.scroller.scrollHeight),f=b.scroller.scrollWidth>b.scroller.clientWidth+1,g=e>b.scroller.clientHeight+1;g?(b.scrollbarV.style.display="block",b.scrollbarV.style.bottom=f?tf(b.measure)+"px":"0",b.scrollbarV.firstChild.style.height=e-b.scroller.clientHeight+b.scrollbarV.clientHeight+"px"):(b.scrollbarV.style.display="",b.scrollbarV.firstChild.style.height="0"),f?(b.scrollbarH.style.display="block",b.scrollbarH.style.right=g?tf(b.measure)+"px":"0",b.scrollbarH.firstChild.style.width=b.scroller.scrollWidth-b.scroller.clientWidth+b.scrollbarH.clientWidth+"px"):(b.scrollbarH.style.display="",b.scrollbarH.firstChild.style.width="0"),f&&g?(b.scrollbarFiller.style.display="block",b.scrollbarFiller.style.height=b.scrollbarFiller.style.width=tf(b.measure)+"px"):b.scrollbarFiller.style.display="",f&&a.options.coverGutterNextToScrollbar&&a.options.fixedGutter?(b.gutterFiller.style.display="block",b.gutterFiller.style.height=tf(b.measure)+"px",b.gutterFiller.style.width=b.gutters.offsetWidth+"px"):b.gutterFiller.style.display="",l&&0===tf(b.measure)&&(b.scrollbarV.style.minWidth=b.scrollbarH.style.minHeight=m?"18px":"12px",b.scrollbarV.style.pointerEvents=b.scrollbarH.style.pointerEvents="none")}function L(a,b,c){var d=a.scroller.scrollTop,e=a.wrapper.clientHeight;"number"==typeof c?d=c:c&&(d=c.top,e=c.bottom-c.top),d=Math.floor(d-fb(a));var f=Math.ceil(d+e);return{from:qe(b,d),to:qe(b,f)}}function M(a){var b=a.display;if(b.alignWidgets||b.gutters.firstChild&&a.options.fixedGutter){for(var c=P(b)-b.scroller.scrollLeft+a.doc.scrollLeft,d=b.gutters.offsetWidth,e=c+"px",f=b.lineDiv.firstChild;f;f=f.nextSibling)if(f.alignable)for(var g=0,h=f.alignable;g<h.length;++g)h[g].style.left=e;a.options.fixedGutter&&(b.gutters.style.left=c+d+"px")}}function N(a){if(!a.options.lineNumbers)return!1;var b=a.doc,c=O(a.options,b.first+b.size-1),d=a.display;if(c.length!=d.lineNumChars){var e=d.measure.appendChild(lf("div",[lf("div",c)],"CodeMirror-linenumber CodeMirror-gutter-elt")),f=e.firstChild.offsetWidth,g=e.offsetWidth-f;return d.lineGutter.style.width="",d.lineNumInnerWidth=Math.max(f,d.lineGutter.offsetWidth-g),d.lineNumWidth=d.lineNumInnerWidth+g,d.lineNumChars=d.lineNumInnerWidth?c.length:-1,d.lineGutter.style.width=d.lineNumWidth+"px",!0}return!1}function O(a,b){return String(a.lineNumberFormatter(b+a.firstLineNumber))}function P(a){return pf(a.scroller).left-pf(a.sizer).left}function Q(a,b,c,d){for(var g,e=a.display.showingFrom,f=a.display.showingTo,h=L(a.display,a.doc,c),i=!0;;i=!1){var j=a.display.scroller.clientWidth;if(!R(a,b,h,d))break;if(g=!0,b=[],Z(a),K(a),i&&a.options.lineWrapping&&j!=a.display.scroller.clientWidth)d=!0;else if(d=!1,c&&(c=Math.min(a.display.scroller.scrollHeight-a.display.scroller.clientHeight,"number"==typeof c?c:c.top)),h=L(a.display,a.doc,c),h.from>=a.display.showingFrom&&h.to<=a.display.showingTo)break}return g&&(Qe(a,"update",a),(a.display.showingFrom!=e||a.display.showingTo!=f)&&Qe(a,"viewportChange",a,a.display.showingFrom,a.display.showingTo)),g}function R(a,b,c,d){var e=a.display,f=a.doc;if(!e.wrapper.clientWidth)return e.showingFrom=e.showingTo=f.first,e.viewOffset=0,void 0;if(!(!d&&0==b.length&&c.from>e.showingFrom&&c.to<e.showingTo)){N(a)&&(b=[{from:f.first,to:f.first+f.size}]);var g=e.sizer.style.marginLeft=e.gutters.offsetWidth+"px";e.scrollbarH.style.left=a.options.fixedGutter?g:"0";var h=1/0;if(a.options.lineNumbers)for(var i=0;i<b.length;++i)b[i].diff&&b[i].from<h&&(h=b[i].from);var j=f.first+f.size,k=Math.max(c.from-a.options.viewportMargin,f.first),l=Math.min(j,c.to+a.options.viewportMargin);if(e.showingFrom<k&&k-e.showingFrom<20&&(k=Math.max(f.first,e.showingFrom)),e.showingTo>l&&e.showingTo-l<20&&(l=Math.min(j,e.showingTo)),w)for(k=pe(Fd(f,le(f,k)));j>l&&Gd(f,le(f,l));)++l;var m=[{from:Math.max(e.showingFrom,f.first),to:Math.min(e.showingTo,j)}];if(m=m[0].from>=m[0].to?[]:U(m,b),w)for(var i=0;i<m.length;++i)for(var o,n=m[i];o=Ed(le(f,n.to-1));){var p=o.find().from.line;if(!(p>n.from)){m.splice(i--,1);break}n.to=p}for(var q=0,i=0;i<m.length;++i){var n=m[i];n.from<k&&(n.from=k),n.to>l&&(n.to=l),n.from>=n.to?m.splice(i--,1):q+=n.to-n.from}if(!d&&q==l-k&&k==e.showingFrom&&l==e.showingTo)return T(a),void 0;m.sort(function(a,b){return a.from-b.from});try{var r=document.activeElement}catch(s){}.7*(l-k)>q&&(e.lineDiv.style.display="none"),W(a,k,l,m,h),e.lineDiv.style.display="",r&&document.activeElement!=r&&r.offsetHeight&&r.focus();var t=k!=e.showingFrom||l!=e.showingTo||e.lastSizeC!=e.wrapper.clientHeight;return t&&(e.lastSizeC=e.wrapper.clientHeight,bb(a,400)),e.showingFrom=k,e.showingTo=l,S(a),T(a),!0}}function S(a){for(var f,b=a.display,d=b.lineDiv.offsetTop,e=b.lineDiv.firstChild;e;e=e.nextSibling)if(e.lineObj){if(c){var g=e.offsetTop+e.offsetHeight;f=g-d,d=g}else{var h=pf(e);f=h.bottom-h.top}var i=e.lineObj.height-f;if(2>f&&(f=Ab(b)),i>.001||-.001>i){oe(e.lineObj,f);var j=e.lineObj.widgets;if(j)for(var k=0;k<j.length;++k)j[k].height=j[k].node.offsetHeight}}}function T(a){var b=a.display.viewOffset=re(a,le(a.doc,a.display.showingFrom));a.display.mover.style.top=b+"px"}function U(a,b){for(var c=0,d=b.length||0;d>c;++c){for(var e=b[c],f=[],g=e.diff||0,h=0,i=a.length;i>h;++h){var j=a[h];e.to<=j.from&&e.diff?f.push({from:j.from+g,to:j.to+g}):e.to<=j.from||e.from>=j.to?f.push(j):(e.from>j.from&&f.push({from:j.from,to:e.from}),e.to<j.to&&f.push({from:e.to+g,to:j.to+g}))}a=f}return a}function V(a){for(var b=a.display,c={},d={},e=b.gutters.firstChild,f=0;e;e=e.nextSibling,++f)c[a.options.gutters[f]]=e.offsetLeft,d[a.options.gutters[f]]=e.offsetWidth;return{fixedPos:P(b),gutterTotalWidth:b.gutters.offsetWidth,gutterLeft:c,gutterWidth:d,wrapperWidth:b.wrapper.clientWidth}}function W(a,b,c,d,e){function l(b){var c=b.nextSibling;return f&&q&&a.display.currentWheelTarget==b?(b.style.display="none",b.lineObj=null):b.parentNode.removeChild(b),c}var g=V(a),h=a.display,i=a.options.lineNumbers;d.length||f&&a.display.currentWheelTarget||mf(h.lineDiv);var j=h.lineDiv,k=j.firstChild,m=d.shift(),n=b;for(a.doc.iter(b,c,function(b){if(m&&m.to==n&&(m=d.shift()),Gd(a.doc,b)){if(0!=b.height&&oe(b,0),b.widgets&&k&&k.previousSibling)for(var c=0;c<b.widgets.length;++c){var f=b.widgets[c];if(f.showIfHidden){var h=k.previousSibling;if(/pre/i.test(h.nodeName)){var o=lf("div",null,null,"position: relative");h.parentNode.replaceChild(o,h),o.appendChild(h),h=o}var p=h.appendChild(lf("div",[f.node],"CodeMirror-linewidget"));f.handleMouseEvents||(p.ignoreEvents=!0),Y(f,p,h,g)}}}else if(m&&m.from<=n&&m.to>n){for(;k.lineObj!=b;)k=l(k);i&&n>=e&&k.lineNumber&&of(k.lineNumber,O(a.options,n)),k=k.nextSibling}else{if(b.widgets)for(var s,q=0,r=k;r&&20>q;++q,r=r.nextSibling)if(r.lineObj==b&&/div/i.test(r.nodeName)){s=r;break}var t=X(a,b,n,g,s);if(t!=s)j.insertBefore(t,k);else{for(;k!=s;)k=l(k);k=k.nextSibling}t.lineObj=b}++n});k;)k=l(k)}function X(a,b,d,e,f){var k,g=Xd(a,b),h=g.pre,i=b.gutterMarkers,j=a.display,l=g.bgClass?g.bgClass+" "+(b.bgClass||""):b.bgClass;if(!(a.options.lineNumbers||i||l||b.wrapClass||b.widgets))return h;if(f){f.alignable=null;for(var q,m=!0,n=0,o=null,p=f.firstChild;p;p=q)if(q=p.nextSibling,/\bCodeMirror-linewidget\b/.test(p.className)){for(var r=0;r<b.widgets.length;++r){var s=b.widgets[r];if(s.node==p.firstChild){s.above||o||(o=p),Y(s,p,f,e),++n;break}}if(r==b.widgets.length){m=!1;break}}else f.removeChild(p);f.insertBefore(h,o),m&&n==b.widgets.length&&(k=f,f.className=b.wrapClass||"")}if(k||(k=lf("div",null,b.wrapClass,"position: relative"),k.appendChild(h)),l&&k.insertBefore(lf("div",null,l+" CodeMirror-linebackground"),k.firstChild),a.options.lineNumbers||i){var t=k.insertBefore(lf("div",null,null,"position: absolute; left: "+(a.options.fixedGutter?e.fixedPos:-e.gutterTotalWidth)+"px"),k.firstChild);if(a.options.fixedGutter&&(k.alignable||(k.alignable=[])).push(t),!a.options.lineNumbers||i&&i["CodeMirror-linenumbers"]||(k.lineNumber=t.appendChild(lf("div",O(a.options,d),"CodeMirror-linenumber CodeMirror-gutter-elt","left: "+e.gutterLeft["CodeMirror-linenumbers"]+"px; width: "+j.lineNumInnerWidth+"px"))),i)for(var u=0;u<a.options.gutters.length;++u){var v=a.options.gutters[u],w=i.hasOwnProperty(v)&&i[v];w&&t.appendChild(lf("div",[w],"CodeMirror-gutter-elt","left: "+e.gutterLeft[v]+"px; width: "+e.gutterWidth[v]+"px"))}}if(c&&(k.style.zIndex=2),b.widgets&&k!=f)for(var r=0,x=b.widgets;r<x.length;++r){var s=x[r],y=lf("div",[s.node],"CodeMirror-linewidget");s.handleMouseEvents||(y.ignoreEvents=!0),Y(s,y,k,e),s.above?k.insertBefore(y,a.options.lineNumbers&&0!=b.height?t:h):k.appendChild(y),Qe(s,"redraw")}return k}function Y(a,b,c,d){if(a.noHScroll){(c.alignable||(c.alignable=[])).push(b);var e=d.wrapperWidth;b.style.left=d.fixedPos+"px",a.coverGutter||(e-=d.gutterTotalWidth,b.style.paddingLeft=d.gutterTotalWidth+"px"),b.style.width=e+"px"}a.coverGutter&&(b.style.zIndex=5,b.style.position="relative",a.noHScroll||(b.style.marginLeft=-d.gutterTotalWidth+"px"))}function Z(a){var b=a.display,c=Cc(a.doc.sel.from,a.doc.sel.to);if(c||a.options.showCursorWhenSelecting?$(a):b.cursor.style.display=b.otherCursor.style.display="none",c?b.selectionDiv.style.display="none":_(a),a.options.moveInputWithCursor){var d=vb(a,a.doc.sel.head,"div"),e=pf(b.wrapper),f=pf(b.lineDiv);b.inputDiv.style.top=Math.max(0,Math.min(b.wrapper.clientHeight-10,d.top+f.top-e.top))+"px",b.inputDiv.style.left=Math.max(0,Math.min(b.wrapper.clientWidth-10,d.left+f.left-e.left))+"px"}}function $(a){var b=a.display,c=vb(a,a.doc.sel.head,"div");b.cursor.style.left=c.left+"px",b.cursor.style.top=c.top+"px",b.cursor.style.height=Math.max(0,c.bottom-c.top)*a.options.cursorHeight+"px",b.cursor.style.display="",c.other?(b.otherCursor.style.display="",b.otherCursor.style.left=c.other.left+"px",b.otherCursor.style.top=c.other.top+"px",b.otherCursor.style.height=.85*(c.other.bottom-c.other.top)+"px"):b.otherCursor.style.display="none"}function _(a){function h(a,b,c,d){0>b&&(b=0),e.appendChild(lf("div",null,"CodeMirror-selected","position: absolute; left: "+a+"px; top: "+b+"px; width: "+(null==c?f-a:c)+"px; height: "+(d-b)+"px"))}function i(b,d,e){function m(c,d){return ub(a,Bc(b,c),"div",i,d)}var k,l,i=le(c,b),j=i.text.length;return Af(se(i),d||0,null==e?j:e,function(a,b,c){var n,o,p,i=m(a,"left");if(a==b)n=i,o=p=i.left;else{if(n=m(b-1,"right"),"rtl"==c){var q=i;i=n,n=q}o=i.left,p=n.right}null==d&&0==a&&(o=g),n.top-i.top>3&&(h(o,i.top,null,i.bottom),o=g,i.bottom<n.top&&h(o,i.bottom,null,n.top)),null==e&&b==j&&(p=f),(!k||i.top<k.top||i.top==k.top&&i.left<k.left)&&(k=i),(!l||n.bottom>l.bottom||n.bottom==l.bottom&&n.right>l.right)&&(l=n),g+1>o&&(o=g),h(o,n.top,p-o,n.bottom)}),{start:k,end:l}}var b=a.display,c=a.doc,d=a.doc.sel,e=document.createDocumentFragment(),f=b.lineSpace.offsetWidth,g=hb(a.display);if(d.from.line==d.to.line)i(d.from.line,d.from.ch,d.to.ch);else{var j=le(c,d.from.line),k=le(c,d.to.line),l=Fd(c,j)==Fd(c,k),m=i(d.from.line,d.from.ch,l?j.text.length:null).end,n=i(d.to.line,l?0:null,d.to.ch).start;l&&(m.top<n.top-2?(h(m.right,m.top,null,m.bottom),h(g,n.top,n.left,n.bottom)):h(m.right,m.top,n.left-m.right,m.bottom)),m.bottom<n.top&&h(g,m.bottom,null,n.top)}nf(b.selectionDiv,e),b.selectionDiv.style.display=""}function ab(a){if(a.state.focused){var b=a.display;clearInterval(b.blinker);var c=!0;b.cursor.style.visibility=b.otherCursor.style.visibility="",a.options.cursorBlinkRate>0&&(b.blinker=setInterval(function(){b.cursor.style.visibility=b.otherCursor.style.visibility=(c=!c)?"":"hidden"},a.options.cursorBlinkRate))}}function bb(a,b){a.doc.mode.startState&&a.doc.frontier<a.display.showingTo&&a.state.highlight.set(b,ff(cb,a))}function cb(a){var b=a.doc;if(b.frontier<b.first&&(b.frontier=b.first),!(b.frontier>=a.display.showingTo)){var f,c=+new Date+a.options.workTime,d=hd(b.mode,eb(a,b.frontier)),e=[];b.iter(b.frontier,Math.min(b.first+b.size,a.display.showingTo+500),function(g){if(b.frontier>=a.display.showingFrom){var h=g.styles;g.styles=Sd(a,g,d);for(var i=!h||h.length!=g.styles.length,j=0;!i&&j<h.length;++j)i=h[j]!=g.styles[j];i&&(f&&f.end==b.frontier?f.end++:e.push(f={start:b.frontier,end:b.frontier+1})),g.stateAfter=hd(b.mode,d)}else Ud(a,g,d),g.stateAfter=0==b.frontier%5?hd(b.mode,d):null;return++b.frontier,+new Date>c?(bb(a,a.options.workDelay),!0):void 0}),e.length&&Fb(a,function(){for(var a=0;a<e.length;++a)Ib(this,e[a].start,e[a].end)})()}}function db(a,b,c){for(var d,e,f=a.doc,g=c?-1:b-(a.doc.mode.innerMode?1e3:100),h=b;h>g;--h){if(h<=f.first)return f.first;var i=le(f,h-1);if(i.stateAfter&&(!c||h<=f.frontier))return h;var j=Ye(i.text,null,a.options.tabSize);(null==e||d>j)&&(e=h-1,d=j)}return e}function eb(a,b,c){var d=a.doc,e=a.display;if(!d.mode.startState)return!0;var f=db(a,b,c),g=f>d.first&&le(d,f-1).stateAfter;return g=g?hd(d.mode,g):id(d.mode),d.iter(f,b,function(c){Ud(a,c,g);var h=f==b-1||0==f%5||f>=e.showingFrom&&f<e.showingTo;c.stateAfter=h?hd(d.mode,g):null,++f}),c&&(d.frontier=f),g}function fb(a){return a.lineSpace.offsetTop}function gb(a){return a.mover.offsetHeight-a.lineSpace.offsetHeight}function hb(a){var b=nf(a.measure,lf("pre",null,null,"text-align: left")).appendChild(lf("span","x"));return b.offsetLeft}function ib(a,b,c,d,e){var f=-1;if(d=d||lb(a,b),d.crude){var g=d.left+c*d.width;return{left:g,right:g+d.width,top:d.top,bottom:d.bottom}}for(var h=c;;h+=f){var i=d[h];if(i)break;0>f&&0==h&&(f=1)}return e=h>c?"left":c>h?"right":e,"left"==e&&i.leftSide?i=i.leftSide:"right"==e&&i.rightSide&&(i=i.rightSide),{left:c>h?i.right:i.left,right:h>c?i.left:i.right,top:i.top,bottom:i.bottom}}function jb(a,b){for(var c=a.display.measureLineCache,d=0;d<c.length;++d){var e=c[d];if(e.text==b.text&&e.markedSpans==b.markedSpans&&a.display.scroller.clientWidth==e.width&&e.classes==b.textClass+"|"+b.wrapClass)return e}}function kb(a,b){var c=jb(a,b);c&&(c.text=c.measure=c.markedSpans=null)}function lb(a,b){var c=jb(a,b);if(c)return c.measure;var d=mb(a,b),e=a.display.measureLineCache,f={text:b.text,width:a.display.scroller.clientWidth,markedSpans:b.markedSpans,measure:d,classes:b.textClass+"|"+b.wrapClass};return 16==e.length?e[++a.display.measureLineCachePos%16]=f:e.push(f),d}function mb(a,e){function t(a){var b=a.top-p.top,c=a.bottom-p.top;c>s&&(c=s),0>b&&(b=0);for(var d=q.length-2;d>=0;d-=2){var e=q[d],f=q[d+1];if(!(e>c||b>f)&&(b>=e&&f>=c||e>=b&&c>=f||Math.min(c,f)-Math.max(b,e)>=c-b>>1)){q[d]=Math.min(b,e),q[d+1]=Math.max(c,f);break}}return 0>d&&(d=q.length,q.push(b,c)),{left:a.left-p.left,right:a.right-p.left,top:d,bottom:null}}function u(a){a.bottom=q[a.top+1],a.top=q[a.top]}if(!a.options.lineWrapping&&e.text.length>=a.options.crudeMeasuringFrom)return nb(a,e);var f=a.display,g=ef(e.text.length),h=Xd(a,e,g,!0).pre;if(b&&!c&&!a.options.lineWrapping&&h.childNodes.length>100){for(var i=document.createDocumentFragment(),j=10,k=h.childNodes.length,l=0,m=Math.ceil(k/j);m>l;++l){for(var n=lf("div",null,null,"display: inline-block"),o=0;j>o&&k;++o)n.appendChild(h.firstChild),--k;i.appendChild(n)}h.appendChild(i)}nf(f.measure,h);var p=pf(f.lineDiv),q=[],r=ef(e.text.length),s=h.offsetHeight;d&&f.measure.first!=h&&nf(f.measure,h);for(var v,l=0;l<g.length;++l)if(v=g[l]){var w=v,x=null;if(/\bCodeMirror-widget\b/.test(v.className)&&v.getClientRects){1==v.firstChild.nodeType&&(w=v.firstChild);var y=w.getClientRects();y.length>1&&(x=r[l]=t(y[0]),x.rightSide=t(y[y.length-1]))}x||(x=r[l]=t(pf(w))),v.measureRight&&(x.right=pf(v.measureRight).left),v.leftSide&&(x.leftSide=t(pf(v.leftSide)))}mf(a.display.measure);for(var v,l=0;l<r.length;++l)(v=r[l])&&(u(v),v.leftSide&&u(v.leftSide),v.rightSide&&u(v.rightSide));return r}function nb(a,b){var c=new Od(b.text.slice(0,100),null);b.textClass&&(c.textClass=b.textClass);var d=mb(a,c),e=ib(a,c,0,d,"left"),f=ib(a,c,99,d,"right");return{crude:!0,top:e.top,left:e.left,bottom:e.bottom,width:(f.right-e.left)/100}}function ob(a,b){var c=!1;if(b.markedSpans)for(var d=0;d<b.markedSpans;++d){var e=b.markedSpans[d];!e.collapsed||null!=e.to&&e.to!=b.text.length||(c=!0)}var f=!c&&jb(a,b);if(f||b.text.length>=a.options.crudeMeasuringFrom)return ib(a,b,b.text.length,f&&f.measure,"right").right;var g=Xd(a,b,null,!0).pre,h=g.appendChild(vf(a.display.measure));return nf(a.display.measure,g),pf(h).right-pf(a.display.lineDiv).left}function pb(a){a.display.measureLineCache.length=a.display.measureLineCachePos=0,a.display.cachedCharWidth=a.display.cachedTextHeight=null,a.options.lineWrapping||(a.display.maxLineChanged=!0),a.display.lineNumChars=null}function qb(){return window.pageXOffset||(document.documentElement||document.body).scrollLeft}function rb(){return window.pageYOffset||(document.documentElement||document.body).scrollTop}function sb(a,b,c,d){if(b.widgets)for(var e=0;e<b.widgets.length;++e)if(b.widgets[e].above){var f=Md(b.widgets[e]);c.top+=f,c.bottom+=f}if("line"==d)return c;d||(d="local");var g=re(a,b);if("local"==d?g+=fb(a.display):g-=a.display.viewOffset,"page"==d||"window"==d){var h=pf(a.display.lineSpace);g+=h.top+("window"==d?0:rb());var i=h.left+("window"==d?0:qb());c.left+=i,c.right+=i}return c.top+=g,c.bottom+=g,c}function tb(a,b,c){if("div"==c)return b;var d=b.left,e=b.top;if("page"==c)d-=qb(),e-=rb();else if("local"==c||!c){var f=pf(a.display.sizer);d+=f.left,e+=f.top}var g=pf(a.display.lineSpace);return{left:d-g.left,top:e-g.top}}function ub(a,b,c,d,e){return d||(d=le(a.doc,b.line)),sb(a,d,ib(a,d,b.ch,null,e),c)}function vb(a,b,c,d,e){function f(b,f){var g=ib(a,d,b,e,f?"right":"left");return f?g.left=g.right:g.right=g.left,sb(a,d,g,c)}function g(a,b){var c=h[b],d=c.level%2;return a==Bf(c)&&b&&c.level<h[b-1].level?(c=h[--b],a=Cf(c)-(c.level%2?0:1),d=!0):a==Cf(c)&&b<h.length-1&&c.level<h[b+1].level&&(c=h[++b],a=Bf(c)-c.level%2,d=!1),d&&a==c.to&&a>c.from?f(a-1):f(a,d)}d=d||le(a.doc,b.line),e||(e=lb(a,d));var h=se(d),i=b.ch;if(!h)return f(i);var j=Jf(h,i),k=g(i,j);return null!=If&&(k.other=g(i,If)),k}function wb(a,b,c,d){var e=new Bc(a,b);return e.xRel=d,c&&(e.outside=!0),e}function xb(a,b,c){var d=a.doc;if(c+=a.display.viewOffset,0>c)return wb(d.first,0,!0,-1);var e=qe(d,c),f=d.first+d.size-1;if(e>f)return wb(d.first+d.size-1,le(d,f).text.length,!0,1);for(0>b&&(b=0);;){var g=le(d,e),h=yb(a,g,e,b,c),i=Ed(g),j=i&&i.find();if(!i||!(h.ch>j.from.ch||h.ch==j.from.ch&&h.xRel>0))return h;e=j.to.line}}function yb(a,b,c,d,e){function j(d){var e=vb(a,Bc(c,d),"line",b,i);return g=!0,f>e.bottom?e.left-h:f<e.top?e.left+h:(g=!1,e.left)}var f=e-re(a,b),g=!1,h=2*a.display.wrapper.clientWidth,i=lb(a,b),k=se(b),l=b.text.length,m=Df(b),n=Ef(b),o=j(m),p=g,q=j(n),r=g;if(d>q)return wb(c,n,r,1);for(;;){if(k?n==m||n==Lf(b,m,1):1>=n-m){for(var s=o>d||q-d>=d-o?m:n,t=d-(s==m?o:q);kf.test(b.text.charAt(s));)++s;var u=wb(c,s,s==m?p:r,0>t?-1:t?1:0);return u}var v=Math.ceil(l/2),w=m+v;if(k){w=m;for(var x=0;v>x;++x)w=Lf(b,w,1)}var y=j(w);y>d?(n=w,q=y,(r=g)&&(q+=1e3),l=v):(m=w,o=y,p=g,l-=v)}}function Ab(a){if(null!=a.cachedTextHeight)return a.cachedTextHeight;if(null==zb){zb=lf("pre");for(var b=0;49>b;++b)zb.appendChild(document.createTextNode("x")),zb.appendChild(lf("br"));zb.appendChild(document.createTextNode("x"))}nf(a.measure,zb);var c=zb.offsetHeight/50;return c>3&&(a.cachedTextHeight=c),mf(a.measure),c||1}function Bb(a){if(null!=a.cachedCharWidth)return a.cachedCharWidth;var b=lf("span","x"),c=lf("pre",[b]);nf(a.measure,c);var d=b.offsetWidth;return d>2&&(a.cachedCharWidth=d),d||10}function Db(a){a.curOp={changes:[],forceUpdate:!1,updateInput:null,userSelChange:null,textChanged:null,selectionChanged:!1,cursorActivity:!1,updateMaxLine:!1,updateScrollPos:!1,id:++Cb},Pe++||(Oe=[])}function Eb(a){var b=a.curOp,c=a.doc,d=a.display;if(a.curOp=null,b.updateMaxLine&&I(a),d.maxLineChanged&&!a.options.lineWrapping&&d.maxLine){var e=ob(a,d.maxLine);d.sizer.style.minWidth=Math.max(0,e+3+Ve)+"px",d.maxLineChanged=!1;var f=Math.max(0,d.sizer.offsetLeft+d.sizer.offsetWidth-d.scroller.clientWidth);f<c.scrollLeft&&!b.updateScrollPos&&ac(a,Math.min(d.scroller.scrollLeft,f),!0)}var g,h;if(b.updateScrollPos)g=b.updateScrollPos;else if(b.selectionChanged&&d.scroller.clientHeight){var i=vb(a,c.sel.head);g=Rc(a,i.left,i.top,i.left,i.bottom)}(b.changes.length||b.forceUpdate||g&&null!=g.scrollTop)&&(h=Q(a,b.changes,g&&g.scrollTop,b.forceUpdate),a.display.scroller.offsetHeight&&(a.doc.scrollTop=a.display.scroller.scrollTop)),!h&&b.selectionChanged&&Z(a),b.updateScrollPos?(d.scroller.scrollTop=d.scrollbarV.scrollTop=c.scrollTop=g.scrollTop,d.scroller.scrollLeft=d.scrollbarH.scrollLeft=c.scrollLeft=g.scrollLeft,M(a),b.scrollToPos&&Pc(a,Gc(a.doc,b.scrollToPos.from),Gc(a.doc,b.scrollToPos.to),b.scrollToPos.margin)):g&&Oc(a),b.selectionChanged&&ab(a),a.state.focused&&b.updateInput&&Mb(a,b.userSelChange);var j=b.maybeHiddenMarkers,k=b.maybeUnhiddenMarkers;if(j)for(var l=0;l<j.length;++l)j[l].lines.length||Ne(j[l],"hide");if(k)for(var l=0;l<k.length;++l)k[l].lines.length&&Ne(k[l],"unhide");var m;if(--Pe||(m=Oe,Oe=null),b.textChanged&&Ne(a,"change",a,b.textChanged),b.cursorActivity&&Ne(a,"cursorActivity",a),m)for(var l=0;l<m.length;++l)m[l]()}function Fb(a,b){return function(){var c=a||this,d=!c.curOp;d&&Db(c);try{var e=b.apply(c,arguments)}finally{d&&Eb(c)}return e}}function Gb(a){return function(){var c,b=this.cm&&!this.cm.curOp;b&&Db(this.cm);try{c=a.apply(this,arguments)}finally{b&&Eb(this.cm)}return c}}function Hb(a,b){var d,c=!a.curOp;c&&Db(a);try{d=b()}finally{c&&Eb(a)}return d}function Ib(a,b,c,d){null==b&&(b=a.doc.first),null==c&&(c=a.doc.first+a.doc.size),a.curOp.changes.push({from:b,to:c,diff:d})}function Jb(a){a.display.pollingFast||a.display.poll.set(a.options.pollInterval,function(){Lb(a),a.state.focused&&Jb(a)})}function Kb(a){function c(){var d=Lb(a);d||b?(a.display.pollingFast=!1,Jb(a)):(b=!0,a.display.poll.set(60,c))}var b=!1;a.display.pollingFast=!0,a.display.poll.set(20,c)}function Lb(a){var c=a.display.input,e=a.display.prevInput,f=a.doc,g=f.sel;if(!a.state.focused||xf(c)||Ob(a)||a.state.disableInput)return!1;a.state.pasteIncoming&&a.state.fakedLastChar&&(c.value=c.value.substring(0,c.value.length-1),a.state.fakedLastChar=!1);var h=c.value;if(h==e&&Cc(g.from,g.to))return!1;if(b&&!d&&a.display.inputHasSelection===h)return Mb(a,!0),!1;var i=!a.curOp;i&&Db(a),g.shift=!1;for(var j=0,k=Math.min(e.length,h.length);k>j&&e.charCodeAt(j)==h.charCodeAt(j);)++j;var l=g.from,m=g.to;j<e.length?l=Bc(l.line,l.ch-(e.length-j)):a.state.overwrite&&Cc(l,m)&&!a.state.pasteIncoming&&(m=Bc(m.line,Math.min(le(f,m.line).text.length,m.ch+(h.length-j))));var n=a.curOp.updateInput,o={from:l,to:m,text:wf(h.slice(j)),origin:a.state.pasteIncoming?"paste":"+input"};return uc(a.doc,o,"end"),a.curOp.updateInput=n,Qe(a,"inputRead",a,o),h.length>1e3||h.indexOf("\n")>-1?c.value=a.display.prevInput="":a.display.prevInput=h,i&&Eb(a),a.state.pasteIncoming=!1,!0}function Mb(a,c){var e,f,g=a.doc;if(Cc(g.sel.from,g.sel.to))c&&(a.display.prevInput=a.display.input.value="",b&&!d&&(a.display.inputHasSelection=null));else{a.display.prevInput="",e=yf&&(g.sel.to.line-g.sel.from.line>100||(f=a.getSelection()).length>1e3);var h=e?"-":f||a.getSelection();a.display.input.value=h,a.state.focused&&af(a.display.input),b&&!d&&(a.display.inputHasSelection=h)}a.display.inaccurateSelection=e}function Nb(a){"nocursor"==a.options.readOnly||p&&document.activeElement==a.display.input||a.display.input.focus()}function Ob(a){return a.options.readOnly||a.doc.cantEdit}function Pb(a){function e(){a.state.focused&&setTimeout(ff(Nb,a),0)}function h(){null==g&&(g=setTimeout(function(){g=null,c.cachedCharWidth=c.cachedTextHeight=sf=null,pb(a),Hb(a,ff(Ib,a))},100))}function i(){for(var a=c.wrapper.parentNode;a&&a!=document.body;a=a.parentNode);a?setTimeout(i,5e3):Me(window,"resize",h)}function j(b){Re(a,b)||a.options.onDragEvent&&a.options.onDragEvent(a,Ee(b))||Ie(b)}function l(){c.inaccurateSelection&&(c.prevInput="",c.inaccurateSelection=!1,c.input.value=a.getSelection(),af(c.input))}var c=a.display;Le(c.scroller,"mousedown",Fb(a,Ub)),b?Le(c.scroller,"dblclick",Fb(a,function(b){if(!Re(a,b)){var c=Rb(a,b);if(c&&!Xb(a,b)&&!Qb(a.display,b)){Fe(b);var d=Yc(le(a.doc,c.line).text,c);Jc(a.doc,d.from,d.to)}}})):Le(c.scroller,"dblclick",function(b){Re(a,b)||Fe(b)}),Le(c.lineSpace,"selectstart",function(a){Qb(c,a)||Fe(a)}),u||Le(c.scroller,"contextmenu",function(b){pc(a,b)}),Le(c.scroller,"scroll",function(){c.scroller.clientHeight&&(_b(a,c.scroller.scrollTop),ac(a,c.scroller.scrollLeft,!0),Ne(a,"scroll",a))}),Le(c.scrollbarV,"scroll",function(){c.scroller.clientHeight&&_b(a,c.scrollbarV.scrollTop)}),Le(c.scrollbarH,"scroll",function(){c.scroller.clientHeight&&ac(a,c.scrollbarH.scrollLeft)}),Le(c.scroller,"mousewheel",function(b){dc(a,b)}),Le(c.scroller,"DOMMouseScroll",function(b){dc(a,b)}),Le(c.scrollbarH,"mousedown",e),Le(c.scrollbarV,"mousedown",e),Le(c.wrapper,"scroll",function(){c.wrapper.scrollTop=c.wrapper.scrollLeft=0});var g;Le(window,"resize",h),setTimeout(i,5e3),Le(c.input,"keyup",Fb(a,function(b){Re(a,b)||a.options.onKeyEvent&&a.options.onKeyEvent(a,Ee(b))||16==b.keyCode&&(a.doc.sel.shift=!1)})),Le(c.input,"input",function(){b&&!d&&a.display.inputHasSelection&&(a.display.inputHasSelection=null),Kb(a)}),Le(c.input,"keydown",Fb(a,kc)),Le(c.input,"keypress",Fb(a,lc)),Le(c.input,"focus",ff(mc,a)),Le(c.input,"blur",ff(nc,a)),a.options.dragDrop&&(Le(c.scroller,"dragstart",function(b){$b(a,b)}),Le(c.scroller,"dragenter",j),Le(c.scroller,"dragover",j),Le(c.scroller,"drop",Fb(a,Zb))),Le(c.scroller,"paste",function(b){Qb(c,b)||(Nb(a),Kb(a))}),Le(c.input,"paste",function(){if(f&&!a.state.fakedLastChar&&!(new Date-a.state.lastMiddleDown<200)){var b=c.input.selectionStart,d=c.input.selectionEnd;c.input.value+="$",c.input.selectionStart=b,c.input.selectionEnd=d,a.state.fakedLastChar=!0}a.state.pasteIncoming=!0,Kb(a)}),Le(c.input,"cut",l),Le(c.input,"copy",l),k&&Le(c.sizer,"mouseup",function(){document.activeElement==c.input&&c.input.blur(),Nb(a)})}function Qb(a,b){for(var c=Je(b);c!=a.wrapper;c=c.parentNode)if(!c||c.ignoreEvents||c.parentNode==a.sizer&&c!=a.mover)return!0}function Rb(a,b,c){var d=a.display;if(!c){var e=Je(b);if(e==d.scrollbarH||e==d.scrollbarH.firstChild||e==d.scrollbarV||e==d.scrollbarV.firstChild||e==d.scrollbarFiller||e==d.gutterFiller)return null}var f,g,h=pf(d.lineSpace);try{f=b.clientX,g=b.clientY}catch(b){return null}return xb(a,f-h.left,g-h.top)}function Ub(a){function q(a){if(!Cc(p,a)){if(p=a,"single"==j)return Jc(c.doc,Gc(e,h),a),void 0;if(n=Gc(e,n),o=Gc(e,o),"double"==j){var b=Yc(le(e,a.line).text,a);Dc(a,n)?Jc(c.doc,b.from,o):Jc(c.doc,n,b.to)
+}else"triple"==j&&(Dc(a,n)?Jc(c.doc,o,Gc(e,Bc(a.line,0))):Jc(c.doc,n,Gc(e,Bc(a.line+1,0))))}}function t(a){var b=++s,f=Rb(c,a,!0);if(f)if(Cc(f,l)){var h=a.clientY<r.top?-20:a.clientY>r.bottom?20:0;h&&setTimeout(Fb(c,function(){s==b&&(d.scroller.scrollTop+=h,t(a))}),50)}else{c.state.focused||mc(c),l=f,q(f);var g=L(d,e);(f.line>=g.to||f.line<g.from)&&setTimeout(Fb(c,function(){s==b&&t(a)}),150)}}function v(a){s=1/0,Fe(a),Nb(c),Me(document,"mousemove",w),Me(document,"mouseup",x)}if(!Re(this,a)){var c=this,d=c.display,e=c.doc,g=e.sel;if(g.shift=a.shiftKey,Qb(d,a))return f||(d.scroller.draggable=!1,setTimeout(function(){d.scroller.draggable=!0},100)),void 0;if(!Xb(c,a)){var h=Rb(c,a);switch(Ke(a)){case 3:return u&&pc.call(c,c,a),void 0;case 2:return f&&(c.state.lastMiddleDown=+new Date),h&&Jc(c.doc,h),setTimeout(ff(Nb,c),20),Fe(a),void 0}if(!h)return Je(a)==d.scroller&&Fe(a),void 0;c.state.focused||mc(c);var i=+new Date,j="single";if(Tb&&Tb.time>i-400&&Cc(Tb.pos,h))j="triple",Fe(a),setTimeout(ff(Nb,c),20),Zc(c,h.line);else if(Sb&&Sb.time>i-400&&Cc(Sb.pos,h)){j="double",Tb={time:i,pos:h},Fe(a);var k=Yc(le(e,h.line).text,h);Jc(c.doc,k.from,k.to)}else Sb={time:i,pos:h};var l=h;if(c.options.dragDrop&&qf&&!Ob(c)&&!Cc(g.from,g.to)&&!Dc(h,g.from)&&!Dc(g.to,h)&&"single"==j){var m=Fb(c,function(b){f&&(d.scroller.draggable=!1),c.state.draggingText=!1,Me(document,"mouseup",m),Me(d.scroller,"drop",m),Math.abs(a.clientX-b.clientX)+Math.abs(a.clientY-b.clientY)<10&&(Fe(b),Jc(c.doc,h),Nb(c))});return f&&(d.scroller.draggable=!0),c.state.draggingText=m,d.scroller.dragDrop&&d.scroller.dragDrop(),Le(document,"mouseup",m),Le(d.scroller,"drop",m),void 0}Fe(a),"single"==j&&Jc(c.doc,Gc(e,h));var n=g.from,o=g.to,p=h,r=pf(d.wrapper),s=0,w=Fb(c,function(a){b||Ke(a)?t(a):v(a)}),x=Fb(c,v);Le(document,"mousemove",w),Le(document,"mouseup",x)}}}function Vb(a,b,c,d,e){try{var f=b.clientX,g=b.clientY}catch(b){return!1}if(f>=Math.floor(pf(a.display.gutters).right))return!1;d&&Fe(b);var h=a.display,i=pf(h.lineDiv);if(g>i.bottom||!Te(a,c))return He(b);g-=i.top-h.viewOffset;for(var j=0;j<a.options.gutters.length;++j){var k=h.gutters.childNodes[j];if(k&&pf(k).right>=f){var l=qe(a.doc,g),m=a.options.gutters[j];return e(a,c,a,l,m,b),He(b)}}}function Wb(a,b){return Te(a,"gutterContextMenu")?Vb(a,b,"gutterContextMenu",!1,Ne):!1}function Xb(a,b){return Vb(a,b,"gutterClick",!0,Qe)}function Zb(a){var c=this;if(!(Re(c,a)||Qb(c.display,a)||c.options.onDragEvent&&c.options.onDragEvent(c,Ee(a)))){Fe(a),b&&(Yb=+new Date);var d=Rb(c,a,!0),e=a.dataTransfer.files;if(d&&!Ob(c))if(e&&e.length&&window.FileReader&&window.File)for(var f=e.length,g=Array(f),h=0,i=function(a,b){var e=new FileReader;e.onload=function(){g[b]=e.result,++h==f&&(d=Gc(c.doc,d),uc(c.doc,{from:d,to:d,text:wf(g.join("\n")),origin:"paste"},"around"))},e.readAsText(a)},j=0;f>j;++j)i(e[j],j);else{if(c.state.draggingText&&!Dc(d,c.doc.sel.from)&&!Dc(c.doc.sel.to,d))return c.state.draggingText(a),setTimeout(ff(Nb,c),20),void 0;try{var g=a.dataTransfer.getData("Text");if(g){var k=c.doc.sel.from,l=c.doc.sel.to;Lc(c.doc,d,d),c.state.draggingText&&Ac(c.doc,"",k,l,"paste"),c.replaceSelection(g,null,"paste"),Nb(c),mc(c)}}catch(a){}}}}function $b(a,c){if(b&&(!a.state.draggingText||+new Date-Yb<100))return Ie(c),void 0;if(!Re(a,c)&&!Qb(a.display,c)){var d=a.getSelection();if(c.dataTransfer.setData("Text",d),c.dataTransfer.setDragImage&&!j){var e=lf("img",null,null,"position: fixed; left: 0; top: 0;");e.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",i&&(e.width=e.height=1,a.display.wrapper.appendChild(e),e._top=e.offsetTop),c.dataTransfer.setDragImage(e,0,0),i&&e.parentNode.removeChild(e)}}}function _b(b,c){Math.abs(b.doc.scrollTop-c)<2||(b.doc.scrollTop=c,a||Q(b,[],c),b.display.scroller.scrollTop!=c&&(b.display.scroller.scrollTop=c),b.display.scrollbarV.scrollTop!=c&&(b.display.scrollbarV.scrollTop=c),a&&Q(b,[]),bb(b,100))}function ac(a,b,c){(c?b==a.doc.scrollLeft:Math.abs(a.doc.scrollLeft-b)<2)||(b=Math.min(b,a.display.scroller.scrollWidth-a.display.scroller.clientWidth),a.doc.scrollLeft=b,M(a),a.display.scroller.scrollLeft!=b&&(a.display.scroller.scrollLeft=b),a.display.scrollbarH.scrollLeft!=b&&(a.display.scrollbarH.scrollLeft=b))}function dc(b,c){var d=c.wheelDeltaX,e=c.wheelDeltaY;null==d&&c.detail&&c.axis==c.HORIZONTAL_AXIS&&(d=c.detail),null==e&&c.detail&&c.axis==c.VERTICAL_AXIS?e=c.detail:null==e&&(e=c.wheelDelta);var g=b.display,h=g.scroller;if(d&&h.scrollWidth>h.clientWidth||e&&h.scrollHeight>h.clientHeight){if(e&&q&&f)for(var j=c.target;j!=h;j=j.parentNode)if(j.lineObj){b.display.currentWheelTarget=j;break}if(d&&!a&&!i&&null!=cc)return e&&_b(b,Math.max(0,Math.min(h.scrollTop+e*cc,h.scrollHeight-h.clientHeight))),ac(b,Math.max(0,Math.min(h.scrollLeft+d*cc,h.scrollWidth-h.clientWidth))),Fe(c),g.wheelStartX=null,void 0;if(e&&null!=cc){var k=e*cc,l=b.doc.scrollTop,m=l+g.wrapper.clientHeight;0>k?l=Math.max(0,l+k-50):m=Math.min(b.doc.height,m+k+50),Q(b,[],{top:l,bottom:m})}20>bc&&(null==g.wheelStartX?(g.wheelStartX=h.scrollLeft,g.wheelStartY=h.scrollTop,g.wheelDX=d,g.wheelDY=e,setTimeout(function(){if(null!=g.wheelStartX){var a=h.scrollLeft-g.wheelStartX,b=h.scrollTop-g.wheelStartY,c=b&&g.wheelDY&&b/g.wheelDY||a&&g.wheelDX&&a/g.wheelDX;g.wheelStartX=g.wheelStartY=null,c&&(cc=(cc*bc+c)/(bc+1),++bc)}},200)):(g.wheelDX+=d,g.wheelDY+=e))}}function ec(a,b,c){if("string"==typeof b&&(b=jd[b],!b))return!1;a.display.pollingFast&&Lb(a)&&(a.display.pollingFast=!1);var d=a.doc,e=d.sel.shift,f=!1;try{Ob(a)&&(a.state.suppressEdits=!0),c&&(d.sel.shift=!1),f=b(a)!=We}finally{d.sel.shift=e,a.state.suppressEdits=!1}return f}function fc(a){var b=a.state.keyMaps.slice(0);return a.options.extraKeys&&b.push(a.options.extraKeys),b.push(a.options.keyMap),b}function hc(a,b){var c=ld(a.options.keyMap),e=c.auto;clearTimeout(gc),e&&!nd(b)&&(gc=setTimeout(function(){ld(a.options.keyMap)==c&&(a.options.keyMap=e.call?e.call(null,a):e,D(a))},50));var f=od(b,!0),g=!1;if(!f)return!1;var h=fc(a);return g=b.shiftKey?md("Shift-"+f,h,function(b){return ec(a,b,!0)})||md(f,h,function(b){return("string"==typeof b?/^go[A-Z]/.test(b):b.motion)?ec(a,b):void 0}):md(f,h,function(b){return ec(a,b)}),g&&(Fe(b),ab(a),d&&(b.oldKeyCode=b.keyCode,b.keyCode=0),Qe(a,"keyHandled",a,f,b)),g}function ic(a,b,c){var d=md("'"+c+"'",fc(a),function(b){return ec(a,b,!0)});return d&&(Fe(b),ab(a),Qe(a,"keyHandled",a,"'"+c+"'",b)),d}function kc(a){var c=this;if(c.state.focused||mc(c),!(Re(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,Ee(a)))){b&&27==a.keyCode&&(a.returnValue=!1);var d=a.keyCode;c.doc.sel.shift=16==d||a.shiftKey;var e=hc(c,a);i&&(jc=e?d:null,!e&&88==d&&!yf&&(q?a.metaKey:a.ctrlKey)&&c.replaceSelection(""))}}function lc(a){var c=this;if(!(Re(c,a)||c.options.onKeyEvent&&c.options.onKeyEvent(c,Ee(a)))){var e=a.keyCode,f=a.charCode;if(i&&e==jc)return jc=null,Fe(a),void 0;if(!(i&&(!a.which||a.which<10)||k)||!hc(c,a)){var g=String.fromCharCode(null==f?e:f);this.options.electricChars&&this.doc.mode.electricChars&&this.options.smartIndent&&!Ob(this)&&this.doc.mode.electricChars.indexOf(g)>-1&&setTimeout(Fb(c,function(){Uc(c,c.doc.sel.to.line,"smart")}),75),ic(c,a,g)||(b&&!d&&(c.display.inputHasSelection=null),Kb(c))}}}function mc(a){"nocursor"!=a.options.readOnly&&(a.state.focused||(Ne(a,"focus",a),a.state.focused=!0,-1==a.display.wrapper.className.search(/\bCodeMirror-focused\b/)&&(a.display.wrapper.className+=" CodeMirror-focused"),a.curOp||(Mb(a,!0),f&&setTimeout(ff(Mb,a,!0),0))),Jb(a),ab(a))}function nc(a){a.state.focused&&(Ne(a,"blur",a),a.state.focused=!1,a.display.wrapper.className=a.display.wrapper.className.replace(" CodeMirror-focused","")),clearInterval(a.display.blinker),setTimeout(function(){a.state.focused||(a.doc.sel.shift=!1)},150)}function pc(a,c){function l(){if(null!=e.input.selectionStart){var a=e.input.value="\u200b"+(Cc(f.from,f.to)?"":e.input.value);e.prevInput="\u200b",e.input.selectionStart=1,e.input.selectionEnd=a.length}}function m(){if(e.inputDiv.style.position="relative",e.input.style.cssText=k,d&&(e.scrollbarV.scrollTop=e.scroller.scrollTop=h),Jb(a),null!=e.input.selectionStart){(!b||d)&&l(),clearTimeout(oc);var c=0,f=function(){" "==e.prevInput&&0==e.input.selectionStart?Fb(a,jd.selectAll)(a):c++<10?oc=setTimeout(f,500):Mb(a)};oc=setTimeout(f,200)}}if(!Re(a,c,"contextmenu")){var e=a.display,f=a.doc.sel;if(!Qb(e,c)&&!Wb(a,c)){var g=Rb(a,c),h=e.scroller.scrollTop;if(g&&!i){var j=a.options.resetSelectionOnContextMenu;j&&(Cc(f.from,f.to)||Dc(g,f.from)||!Dc(g,f.to))&&Fb(a,Lc)(a.doc,g,g);var k=e.input.style.cssText;if(e.inputDiv.style.position="absolute",e.input.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(c.clientY-5)+"px; left: "+(c.clientX-5)+"px; z-index: 1000; background: white; outline: none;"+"border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);",Nb(a),Mb(a,!0),Cc(f.from,f.to)&&(e.input.value=e.prevInput=" "),b&&!d&&l(),u){Ie(c);var n=function(){Me(window,"mouseup",n),setTimeout(m,20)};Le(window,"mouseup",n)}else setTimeout(m,50)}}}}function rc(a,b,c){if(!Dc(b.from,c))return Gc(a,c);var d=b.text.length-1-(b.to.line-b.from.line);if(c.line>b.to.line+d){var e=c.line-d,f=a.first+a.size-1;return e>f?Bc(f,le(a,f).text.length):Hc(c,le(a,e).text.length)}if(c.line==b.to.line+d)return Hc(c,_e(b.text).length+(1==b.text.length?b.from.ch:0)+le(a,b.to.line).text.length-b.to.ch);var g=c.line-b.from.line;return Hc(c,b.text[g].length+(g?0:b.from.ch))}function sc(a,b,c){if(c&&"object"==typeof c)return{anchor:rc(a,b,c.anchor),head:rc(a,b,c.head)};if("start"==c)return{anchor:b.from,head:b.from};var d=qc(b);if("around"==c)return{anchor:b.from,head:d};if("end"==c)return{anchor:d,head:d};var e=function(a){if(Dc(a,b.from))return a;if(!Dc(b.to,a))return d;var c=a.line+b.text.length-(b.to.line-b.from.line)-1,e=a.ch;return a.line==b.to.line&&(e+=d.ch-b.to.ch),Bc(c,e)};return{anchor:e(a.sel.anchor),head:e(a.sel.head)}}function tc(a,b,c){var d={canceled:!1,from:b.from,to:b.to,text:b.text,origin:b.origin,cancel:function(){this.canceled=!0}};return c&&(d.update=function(b,c,d,e){b&&(this.from=Gc(a,b)),c&&(this.to=Gc(a,c)),d&&(this.text=d),void 0!==e&&(this.origin=e)}),Ne(a,"beforeChange",a,d),a.cm&&Ne(a.cm,"beforeChange",a.cm,d),d.canceled?null:{from:d.from,to:d.to,text:d.text,origin:d.origin}}function uc(a,b,c,d){if(a.cm){if(!a.cm.curOp)return Fb(a.cm,uc)(a,b,c,d);if(a.cm.state.suppressEdits)return}if(!(Te(a,"beforeChange")||a.cm&&Te(a.cm,"beforeChange"))||(b=tc(a,b,!0))){var e=v&&!d&&Bd(a,b.from,b.to);if(e){for(var f=e.length-1;f>=1;--f)vc(a,{from:e[f].from,to:e[f].to,text:[""]});e.length&&vc(a,{from:e[0].from,to:e[0].to,text:b.text},c)}else vc(a,b,c)}}function vc(a,b,c){if(1!=b.text.length||""!=b.text[0]||!Cc(b.from,b.to)){var d=sc(a,b,c);we(a,b,d,a.cm?a.cm.curOp.id:0/0),yc(a,b,d,zd(a,b));var e=[];je(a,function(a,c){c||-1!=bf(e,a.history)||(Ce(a.history,b),e.push(a.history)),yc(a,b,null,zd(a,b))})}}function wc(a,b){if(!a.cm||!a.cm.state.suppressEdits){var c=a.history,d=("undo"==b?c.done:c.undone).pop();if(d){var e={changes:[],anchorBefore:d.anchorAfter,headBefore:d.headAfter,anchorAfter:d.anchorBefore,headAfter:d.headBefore,generation:c.generation};("undo"==b?c.undone:c.done).push(e),c.generation=d.generation||++c.maxGeneration;for(var f=Te(a,"beforeChange")||a.cm&&Te(a.cm,"beforeChange"),g=d.changes.length-1;g>=0;--g){var h=d.changes[g];if(h.origin=b,f&&!tc(a,h,!1))return("undo"==b?c.done:c.undone).length=0,void 0;e.changes.push(ve(a,h));var i=g?sc(a,h,null):{anchor:d.anchorBefore,head:d.headBefore};yc(a,h,i,Ad(a,h));var j=[];je(a,function(a,b){b||-1!=bf(j,a.history)||(Ce(a.history,h),j.push(a.history)),yc(a,h,null,Ad(a,h))})}}}}function xc(a,b){function c(a){return Bc(a.line+b,a.ch)}a.first+=b,a.cm&&Ib(a.cm,a.first,a.first,b),a.sel.head=c(a.sel.head),a.sel.anchor=c(a.sel.anchor),a.sel.from=c(a.sel.from),a.sel.to=c(a.sel.to)}function yc(a,b,c,d){if(a.cm&&!a.cm.curOp)return Fb(a.cm,yc)(a,b,c,d);if(b.to.line<a.first)return xc(a,b.text.length-1-(b.to.line-b.from.line)),void 0;if(!(b.from.line>a.lastLine())){if(b.from.line<a.first){var e=b.text.length-1-(a.first-b.from.line);xc(a,e),b={from:Bc(a.first,0),to:Bc(b.to.line+e,b.to.ch),text:[_e(b.text)],origin:b.origin}}var f=a.lastLine();b.to.line>f&&(b={from:b.from,to:Bc(f,le(a,f).text.length),text:[b.text[0]],origin:b.origin}),b.removed=me(a,b.from,b.to),c||(c=sc(a,b,null)),a.cm?zc(a.cm,b,d,c):ce(a,b,d,c)}}function zc(a,b,c,d){var e=a.doc,f=a.display,g=b.from,h=b.to,i=!1,j=g.line;a.options.lineWrapping||(j=pe(Fd(e,le(e,g.line))),e.iter(j,h.line+1,function(a){return a==f.maxLine?(i=!0,!0):void 0})),Dc(e.sel.head,b.from)||Dc(b.to,e.sel.head)||(a.curOp.cursorActivity=!0),ce(e,b,c,d,B(a)),a.options.lineWrapping||(e.iter(j,g.line+b.text.length,function(a){var b=H(e,a);b>f.maxLineLength&&(f.maxLine=a,f.maxLineLength=b,f.maxLineChanged=!0,i=!1)}),i&&(a.curOp.updateMaxLine=!0)),e.frontier=Math.min(e.frontier,g.line),bb(a,400);var k=b.text.length-(h.line-g.line)-1;if(Ib(a,g.line,h.line+1,k),Te(a,"change")){var l={from:g,to:h,text:b.text,removed:b.removed,origin:b.origin};if(a.curOp.textChanged){for(var m=a.curOp.textChanged;m.next;m=m.next);m.next=l}else a.curOp.textChanged=l}}function Ac(a,b,c,d,e){if(d||(d=c),Dc(d,c)){var f=d;d=c,c=f}"string"==typeof b&&(b=wf(b)),uc(a,{from:c,to:d,text:b,origin:e},null)}function Bc(a,b){return this instanceof Bc?(this.line=a,this.ch=b,void 0):new Bc(a,b)}function Cc(a,b){return a.line==b.line&&a.ch==b.ch}function Dc(a,b){return a.line<b.line||a.line==b.line&&a.ch<b.ch}function Ec(a){return Bc(a.line,a.ch)}function Fc(a,b){return Math.max(a.first,Math.min(b,a.first+a.size-1))}function Gc(a,b){if(b.line<a.first)return Bc(a.first,0);var c=a.first+a.size-1;return b.line>c?Bc(c,le(a,c).text.length):Hc(b,le(a,b.line).text.length)}function Hc(a,b){var c=a.ch;return null==c||c>b?Bc(a.line,b):0>c?Bc(a.line,0):a}function Ic(a,b){return b>=a.first&&b<a.first+a.size}function Jc(a,b,c,d){if(a.sel.shift||a.sel.extend){var e=a.sel.anchor;if(c){var f=Dc(b,e);f!=Dc(c,e)?(e=b,b=c):f!=Dc(b,c)&&(b=c)}Lc(a,e,b,d)}else Lc(a,b,c||b,d);a.cm&&(a.cm.curOp.userSelChange=!0)}function Kc(a,b,c){var d={anchor:b,head:c};return Ne(a,"beforeSelectionChange",a,d),a.cm&&Ne(a.cm,"beforeSelectionChange",a.cm,d),d.anchor=Gc(a,d.anchor),d.head=Gc(a,d.head),d}function Lc(a,b,c,d,e){if(!e&&Te(a,"beforeSelectionChange")||a.cm&&Te(a.cm,"beforeSelectionChange")){var f=Kc(a,b,c);c=f.head,b=f.anchor}var g=a.sel;if(g.goalColumn=null,null==d&&(d=Dc(c,g.head)?-1:1),(e||!Cc(b,g.anchor))&&(b=Nc(a,b,d,"push"!=e)),(e||!Cc(c,g.head))&&(c=Nc(a,c,d,"push"!=e)),!Cc(g.anchor,b)||!Cc(g.head,c)){g.anchor=b,g.head=c;var h=Dc(c,b);g.from=h?c:b,g.to=h?b:c,a.cm&&(a.cm.curOp.updateInput=a.cm.curOp.selectionChanged=a.cm.curOp.cursorActivity=!0),Qe(a,"cursorActivity",a)}}function Mc(a){Lc(a.doc,a.doc.sel.from,a.doc.sel.to,null,"push")}function Nc(a,b,c,d){var e=!1,f=b,g=c||1;a.cantEdit=!1;a:for(;;){var h=le(a,f.line);if(h.markedSpans)for(var i=0;i<h.markedSpans.length;++i){var j=h.markedSpans[i],k=j.marker;if((null==j.from||(k.inclusiveLeft?j.from<=f.ch:j.from<f.ch))&&(null==j.to||(k.inclusiveRight?j.to>=f.ch:j.to>f.ch))){if(d&&(Ne(k,"beforeCursorEnter"),k.explicitlyCleared)){if(h.markedSpans){--i;continue}break}if(!k.atomic)continue;var l=k.find()[0>g?"from":"to"];if(Cc(l,f)&&(l.ch+=g,l.ch<0?l=l.line>a.first?Gc(a,Bc(l.line-1)):null:l.ch>h.text.length&&(l=l.line<a.first+a.size-1?Bc(l.line+1,0):null),!l)){if(e)return d?(a.cantEdit=!0,Bc(a.first,0)):Nc(a,b,c,!0);e=!0,l=b,g=-g}f=l;continue a}}return f}}function Oc(a){var b=Pc(a,a.doc.sel.head,null,a.options.cursorScrollMargin);if(a.state.focused){var c=a.display,d=pf(c.sizer),e=null;if(b.top+d.top<0?e=!0:b.bottom+d.top>(window.innerHeight||document.documentElement.clientHeight)&&(e=!1),null!=e&&!n){var f="none"==c.cursor.style.display;f&&(c.cursor.style.display="",c.cursor.style.left=b.left+"px",c.cursor.style.top=b.top-c.viewOffset+"px"),c.cursor.scrollIntoView(e),f&&(c.cursor.style.display="none")}}}function Pc(a,b,c,d){for(null==d&&(d=0);;){var e=!1,f=vb(a,b),g=c&&c!=b?vb(a,c):f,h=Rc(a,Math.min(f.left,g.left),Math.min(f.top,g.top)-d,Math.max(f.left,g.left),Math.max(f.bottom,g.bottom)+d),i=a.doc.scrollTop,j=a.doc.scrollLeft;if(null!=h.scrollTop&&(_b(a,h.scrollTop),Math.abs(a.doc.scrollTop-i)>1&&(e=!0)),null!=h.scrollLeft&&(ac(a,h.scrollLeft),Math.abs(a.doc.scrollLeft-j)>1&&(e=!0)),!e)return f}}function Qc(a,b,c,d,e){var f=Rc(a,b,c,d,e);null!=f.scrollTop&&_b(a,f.scrollTop),null!=f.scrollLeft&&ac(a,f.scrollLeft)}function Rc(a,b,c,d,e){var f=a.display,g=Ab(a.display);0>c&&(c=0);var h=f.scroller.clientHeight-Ve,i=f.scroller.scrollTop,j={},k=a.doc.height+gb(f),l=g>c,m=e>k-g;if(i>c)j.scrollTop=l?0:c;else if(e>i+h){var n=Math.min(c,(m?k:e)-h);n!=i&&(j.scrollTop=n)}var o=f.scroller.clientWidth-Ve,p=f.scroller.scrollLeft;b+=f.gutters.offsetWidth,d+=f.gutters.offsetWidth;var q=f.gutters.offsetWidth,r=q+10>b;return p+q>b||r?(r&&(b=0),j.scrollLeft=Math.max(0,b-10-q)):d>o+p-3&&(j.scrollLeft=d+10-o),j}function Sc(a,b,c){a.curOp.updateScrollPos={scrollLeft:null==b?a.doc.scrollLeft:b,scrollTop:null==c?a.doc.scrollTop:c}}function Tc(a,b,c){var d=a.curOp.updateScrollPos||(a.curOp.updateScrollPos={scrollLeft:a.doc.scrollLeft,scrollTop:a.doc.scrollTop}),e=a.display.scroller;d.scrollTop=Math.max(0,Math.min(e.scrollHeight-e.clientHeight,d.scrollTop+c)),d.scrollLeft=Math.max(0,Math.min(e.scrollWidth-e.clientWidth,d.scrollLeft+b))}function Uc(a,b,c,d){var e=a.doc;if(null==c&&(c="add"),"smart"==c)if(a.doc.mode.indent)var f=eb(a,b);else c="prev";var k,g=a.options.tabSize,h=le(e,b),i=Ye(h.text,null,g),j=h.text.match(/^\s*/)[0];if("smart"==c&&(k=a.doc.mode.indent(f,h.text.slice(j.length),h.text),k==We)){if(!d)return;c="prev"}"prev"==c?k=b>e.first?Ye(le(e,b-1).text,null,g):0:"add"==c?k=i+a.options.indentUnit:"subtract"==c?k=i-a.options.indentUnit:"number"==typeof c&&(k=i+c),k=Math.max(0,k);var l="",m=0;if(a.options.indentWithTabs)for(var n=Math.floor(k/g);n;--n)m+=g,l+=" ";k>m&&(l+=$e(k-m)),l!=j?Ac(a.doc,l,Bc(b,0),Bc(b,j.length),"+input"):e.sel.head.line==b&&e.sel.head.ch<j.length&&Lc(e,Bc(b,j.length),Bc(b,j.length),1),h.stateAfter=null}function Vc(a,b,c){var d=b,e=b,f=a.doc;return"number"==typeof b?e=le(f,Fc(f,b)):d=pe(b),null==d?null:c(e,d)?(Ib(a,d,d+1),e):null}function Wc(a,b,c,d,e){function k(){var b=f+c;return b<a.first||b>=a.first+a.size?j=!1:(f=b,i=le(a,b))}function l(a){var b=(e?Lf:Mf)(i,g,c,!0);if(null==b){if(a||!k())return j=!1;g=e?(0>c?Ef:Df)(i):0>c?i.text.length:0}else g=b;return!0}var f=b.line,g=b.ch,h=c,i=le(a,f),j=!0;if("char"==d)l();else if("column"==d)l(!0);else if("word"==d||"group"==d)for(var m=null,n="group"==d,o=!0;!(0>c)||l(!o);o=!1){var p=i.text.charAt(g)||"\n",q=hf(p)?"w":n?/\s/.test(p)?null:"p":null;if(m&&m!=q){0>c&&(c=1,l());break}if(q&&(m=q),c>0&&!l(!o))break}var r=Nc(a,Bc(f,g),h,!0);return j||(r.hitSide=!0),r}function Xc(a,b,c,d){var g,e=a.doc,f=b.left;if("page"==d){var h=Math.min(a.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);g=b.top+c*(h-(0>c?1.5:.5)*Ab(a.display))}else"line"==d&&(g=c>0?b.bottom+3:b.top-3);for(;;){var i=xb(a,f,g);if(!i.outside)break;if(0>c?0>=g:g>=e.height){i.hitSide=!0;break}g+=5*c}return i}function Yc(a,b){var c=b.ch,d=b.ch;if(a){(b.xRel<0||d==a.length)&&c?--c:++d;for(var e=a.charAt(c),f=hf(e)?hf:/\s/.test(e)?function(a){return/\s/.test(a)}:function(a){return!/\s/.test(a)&&!hf(a)};c>0&&f(a.charAt(c-1));)--c;for(;d<a.length&&f(a.charAt(d));)++d}return{from:Bc(b.line,c),to:Bc(b.line,d)}}function Zc(a,b){Jc(a.doc,Bc(b,0),Gc(a.doc,Bc(b+1,0)))}function ad(a,b,c,d){x.defaults[a]=b,c&&($c[a]=d?function(a,b,d){d!=bd&&c(a,b,d)}:c)}function hd(a,b){if(b===!0)return b;if(a.copyState)return a.copyState(b);var c={};for(var d in b){var e=b[d];e instanceof Array&&(e=e.concat([])),c[d]=e}return c}function id(a,b,c){return a.startState?a.startState(b,c):!0}function ld(a){return"string"==typeof a?kd[a]:a}function md(a,b,c){function d(b){b=ld(b);var e=b[a];if(e===!1)return"stop";if(null!=e&&c(e))return!0;if(b.nofallthrough)return"stop";var f=b.fallthrough;if(null==f)return!1;if("[object Array]"!=Object.prototype.toString.call(f))return d(f);for(var g=0,h=f.length;h>g;++g){var i=d(f[g]);if(i)return i}return!1}for(var e=0;e<b.length;++e){var f=d(b[e]);if(f)return"stop"!=f}}function nd(a){var b=zf[a.keyCode];return"Ctrl"==b||"Alt"==b||"Shift"==b||"Mod"==b}function od(a,b){if(i&&34==a.keyCode&&a["char"])return!1;var c=zf[a.keyCode];return null==c||a.altGraphKey?!1:(a.altKey&&(c="Alt-"+c),(t?a.metaKey:a.ctrlKey)&&(c="Ctrl-"+c),(t?a.ctrlKey:a.metaKey)&&(c="Cmd-"+c),!b&&a.shiftKey&&(c="Shift-"+c),c)}function pd(a,b){this.pos=this.start=0,this.string=a,this.tabSize=b||8,this.lastColumnPos=this.lastColumnValue=0}function qd(a,b){this.lines=[],this.type=b,this.doc=a}function rd(a,b,c,d,e){if(d&&d.shared)return td(a,b,c,d,e);if(a.cm&&!a.cm.curOp)return Fb(a.cm,rd)(a,b,c,d,e);var f=new qd(a,e);if(Dc(c,b)||Cc(b,c)&&"range"==e&&(!d.inclusiveLeft||!d.inclusiveRight))return f;d&&df(d,f),f.replacedWith&&(f.collapsed=!0,f.replacedWith=lf("span",[f.replacedWith],"CodeMirror-widget"),d.handleMouseEvents||(f.replacedWith.ignoreEvents=!0)),f.collapsed&&(w=!0),f.addToHistory&&we(a,{from:b,to:c,origin:"markText"},{head:a.sel.head,anchor:a.sel.anchor},0/0);var i,j,l,g=b.line,h=0,k=a.cm;if(a.iter(g,c.line+1,function(d){k&&f.collapsed&&!k.options.lineWrapping&&Fd(a,d)==k.display.maxLine&&(l=!0);var e={from:null,to:null,marker:f};h+=d.text.length,g==b.line&&(e.from=b.ch,h-=b.ch),g==c.line&&(e.to=c.ch,h-=d.text.length-c.ch),f.collapsed&&(g==c.line&&(j=Cd(d,c.ch)),g==b.line?i=Cd(d,b.ch):oe(d,0)),wd(d,e),++g}),f.collapsed&&a.iter(b.line,c.line+1,function(b){Gd(a,b)&&oe(b,0)}),f.clearOnEnter&&Le(f,"beforeCursorEnter",function(){f.clear()}),f.readOnly&&(v=!0,(a.history.done.length||a.history.undone.length)&&a.clearHistory()),f.collapsed){if(i!=j)throw new Error("Inserting collapsed marker overlapping an existing one");f.size=h,f.atomic=!0}return k&&(l&&(k.curOp.updateMaxLine=!0),(f.className||f.title||f.startStyle||f.endStyle||f.collapsed)&&Ib(k,b.line,c.line+1),f.atomic&&Mc(k)),f}function sd(a,b){this.markers=a,this.primary=b;for(var c=0,d=this;c<a.length;++c)a[c].parent=this,Le(a[c],"clear",function(){d.clear()})}function td(a,b,c,d,e){d=df(d),d.shared=!1;var f=[rd(a,b,c,d,e)],g=f[0],h=d.replacedWith;return je(a,function(a){h&&(d.replacedWith=h.cloneNode(!0)),f.push(rd(a,Gc(a,b),Gc(a,c),d,e));for(var i=0;i<a.linked.length;++i)if(a.linked[i].isParent)return;g=_e(f)}),new sd(f,g)}function ud(a,b){if(a)for(var c=0;c<a.length;++c){var d=a[c];if(d.marker==b)return d}}function vd(a,b){for(var c,d=0;d<a.length;++d)a[d]!=b&&(c||(c=[])).push(a[d]);return c}function wd(a,b){a.markedSpans=a.markedSpans?a.markedSpans.concat([b]):[b],b.marker.attachLine(a)}function xd(a,b,c){if(a)for(var e,d=0;d<a.length;++d){var f=a[d],g=f.marker,h=null==f.from||(g.inclusiveLeft?f.from<=b:f.from<b);if(h||(g.inclusiveLeft&&g.inclusiveRight||"bookmark"==g.type)&&f.from==b&&(!c||!f.marker.insertLeft)){var i=null==f.to||(g.inclusiveRight?f.to>=b:f.to>b);(e||(e=[])).push({from:f.from,to:i?null:f.to,marker:g})}}return e}function yd(a,b,c){if(a)for(var e,d=0;d<a.length;++d){var f=a[d],g=f.marker,h=null==f.to||(g.inclusiveRight?f.to>=b:f.to>b);if(h||"bookmark"==g.type&&f.from==b&&(!c||f.marker.insertLeft)){var i=null==f.from||(g.inclusiveLeft?f.from<=b:f.from<b);(e||(e=[])).push({from:i?null:f.from-b,to:null==f.to?null:f.to-b,marker:g})}}return e}function zd(a,b){var c=Ic(a,b.from.line)&&le(a,b.from.line).markedSpans,d=Ic(a,b.to.line)&&le(a,b.to.line).markedSpans;if(!c&&!d)return null;var e=b.from.ch,f=b.to.ch,g=Cc(b.from,b.to),h=xd(c,e,g),i=yd(d,f,g),j=1==b.text.length,k=_e(b.text).length+(j?e:0);if(h)for(var l=0;l<h.length;++l){var m=h[l];if(null==m.to){var n=ud(i,m.marker);n?j&&(m.to=null==n.to?null:n.to+k):m.to=e}}if(i)for(var l=0;l<i.length;++l){var m=i[l];if(null!=m.to&&(m.to+=k),null==m.from){var n=ud(h,m.marker);n||(m.from=k,j&&(h||(h=[])).push(m))}else m.from+=k,j&&(h||(h=[])).push(m)}if(j&&h){for(var l=0;l<h.length;++l)null!=h[l].from&&h[l].from==h[l].to&&"bookmark"!=h[l].marker.type&&h.splice(l--,1);h.length||(h=null)}var o=[h];if(!j){var q,p=b.text.length-2;if(p>0&&h)for(var l=0;l<h.length;++l)null==h[l].to&&(q||(q=[])).push({from:null,to:null,marker:h[l].marker});for(var l=0;p>l;++l)o.push(q);o.push(i)}return o}function Ad(a,b){var c=ye(a,b),d=zd(a,b);if(!c)return d;if(!d)return c;for(var e=0;e<c.length;++e){var f=c[e],g=d[e];if(f&&g)a:for(var h=0;h<g.length;++h){for(var i=g[h],j=0;j<f.length;++j)if(f[j].marker==i.marker)continue a;f.push(i)}else g&&(c[e]=g)}return c}function Bd(a,b,c){var d=null;if(a.iter(b.line,c.line+1,function(a){if(a.markedSpans)for(var b=0;b<a.markedSpans.length;++b){var c=a.markedSpans[b].marker;!c.readOnly||d&&-1!=bf(d,c)||(d||(d=[])).push(c)}}),!d)return null;for(var e=[{from:b,to:c}],f=0;f<d.length;++f)for(var g=d[f],h=g.find(),i=0;i<e.length;++i){var j=e[i];if(!Dc(j.to,h.from)&&!Dc(h.to,j.from)){var k=[i,1];(Dc(j.from,h.from)||!g.inclusiveLeft&&Cc(j.from,h.from))&&k.push({from:j.from,to:h.from}),(Dc(h.to,j.to)||!g.inclusiveRight&&Cc(j.to,h.to))&&k.push({from:h.to,to:j.to}),e.splice.apply(e,k),i+=k.length-1}}return e}function Cd(a,b){var d,c=w&&a.markedSpans;if(c)for(var e,f=0;f<c.length;++f)e=c[f],e.marker.collapsed&&(null==e.from||e.from<b)&&(null==e.to||e.to>b)&&(!d||d.width<e.marker.width)&&(d=e.marker);return d}function Dd(a){return Cd(a,-1)}function Ed(a){return Cd(a,a.text.length+1)}function Fd(a,b){for(var c;c=Dd(b);)b=le(a,c.find().from.line);return b}function Gd(a,b){var c=w&&b.markedSpans;if(c)for(var d,e=0;e<c.length;++e)if(d=c[e],d.marker.collapsed){if(null==d.from)return!0;if(!d.marker.replacedWith&&0==d.from&&d.marker.inclusiveLeft&&Hd(a,b,d))return!0}}function Hd(a,b,c){if(null==c.to){var d=c.marker.find().to,e=le(a,d.line);return Hd(a,e,ud(e.markedSpans,c.marker))}if(c.marker.inclusiveRight&&c.to==b.text.length)return!0;for(var f,g=0;g<b.markedSpans.length;++g)if(f=b.markedSpans[g],f.marker.collapsed&&!f.marker.replacedWith&&f.from==c.to&&(f.marker.inclusiveLeft||c.marker.inclusiveRight)&&Hd(a,b,f))return!0}function Id(a){var b=a.markedSpans;if(b){for(var c=0;c<b.length;++c)b[c].marker.detachLine(a);a.markedSpans=null}}function Jd(a,b){if(b){for(var c=0;c<b.length;++c)b[c].marker.attachLine(a);a.markedSpans=b}}function Ld(a){return function(){var b=!this.cm.curOp;b&&Db(this.cm);try{var c=a.apply(this,arguments)}finally{b&&Eb(this.cm)}return c}}function Md(a){return null!=a.height?a.height:(a.node.parentNode&&1==a.node.parentNode.nodeType||nf(a.cm.display.measure,lf("div",[a.node],null,"position: relative")),a.height=a.node.offsetHeight)}function Nd(a,b,c,d){var e=new Kd(a,c,d);return e.noHScroll&&(a.display.alignWidgets=!0),Vc(a,b,function(b){var c=b.widgets||(b.widgets=[]);if(null==e.insertAt?c.push(e):c.splice(Math.min(c.length-1,Math.max(0,e.insertAt)),0,e),e.line=b,!Gd(a.doc,b)||e.showIfHidden){var d=re(a,b)<a.doc.scrollTop;oe(b,b.height+Md(e)),d&&Tc(a,0,e.height)}return!0}),e}function Pd(a,b,c,d){a.text=b,a.stateAfter&&(a.stateAfter=null),a.styles&&(a.styles=null),null!=a.order&&(a.order=null),Id(a),Jd(a,c);var e=d?d(a):1;e!=a.height&&oe(a,e)}function Qd(a){a.parent=null,Id(a)}function Rd(a,b,c,d,e){var f=c.flattenSpans;null==f&&(f=a.options.flattenSpans);var j,g=0,h=null,i=new pd(b,a.options.tabSize);for(""==b&&c.blankLine&&c.blankLine(d);!i.eol();)i.pos>a.options.maxHighlightLength?(f=!1,i.pos=b.length,j=null):j=c.token(i,d),f&&h==j||(g<i.start&&e(i.start,h),g=i.start,h=j),i.start=i.pos;for(;g<i.pos;){var k=Math.min(i.pos,g+5e4);e(k,h),g=k}}function Sd(a,b,c){var d=[a.state.modeGen];Rd(a,b.text,a.doc.mode,c,function(a,b){d.push(a,b)});for(var e=0;e<a.state.overlays.length;++e){var f=a.state.overlays[e],g=1,h=0;Rd(a,b.text,f.mode,!0,function(a,b){for(var c=g;a>h;){var e=d[g];e>a&&d.splice(g,1,a,d[g+1],e),g+=2,h=Math.min(a,e)}if(b)if(f.opaque)d.splice(c,g-c,a,b),g=c+2;else for(;g>c;c+=2){var i=d[c+1];d[c+1]=i?i+" "+b:b}})}return d}function Td(a,b){return b.styles&&b.styles[0]==a.state.modeGen||(b.styles=Sd(a,b,b.stateAfter=eb(a,pe(b)))),b.styles}function Ud(a,b,c){var d=a.doc.mode,e=new pd(b.text,a.options.tabSize);for(""==b.text&&d.blankLine&&d.blankLine(c);!e.eol()&&e.pos<=a.options.maxHighlightLength;)d.token(e,c),e.start=e.pos}function Wd(a,b){if(!a)return null;for(;;){var c=a.match(/(?:^|\s)line-(background-)?(\S+)/);if(!c)break;a=a.slice(0,c.index)+a.slice(c.index+c[0].length);var d=c[1]?"bgClass":"textClass";null==b[d]?b[d]=c[2]:new RegExp("(?:^|s)"+c[2]+"(?:$|s)").test(b[d])||(b[d]+=" "+c[2])}return Vd[a]||(Vd[a]="cm-"+a.replace(/ +/g," cm-"))}function Xd(a,c,d,g){for(var h,i=c,j=!0;h=Dd(i);)i=le(a.doc,h.find().from.line);var k={pre:lf("pre"),col:0,pos:0,measure:null,measuredSomething:!1,cm:a,copyWidgets:g};do{i.text&&(j=!1),k.measure=i==c&&d,k.pos=0,k.addToken=k.measure?$d:Zd,(b||f)&&a.getOption("lineWrapping")&&(k.addToken=_d(k.addToken));var l=be(i,k,Td(a,i));d&&i==c&&!k.measuredSomething&&(d[0]=k.pre.appendChild(vf(a.display.measure)),k.measuredSomething=!0),l&&(i=le(a.doc,l.to.line))}while(l);!d||k.measuredSomething||d[0]||(d[0]=k.pre.appendChild(j?lf("span","\xa0"):vf(a.display.measure))),k.pre.firstChild||Gd(a.doc,c)||k.pre.appendChild(document.createTextNode("\xa0"));var m;if(d&&(b||e)&&(m=se(i))){var n=m.length-1;m[n].from==m[n].to&&--n;var o=m[n],p=m[n-1];if(o.from+1==o.to&&p&&o.level<p.level){var q=d[k.pos-1];q&&q.parentNode.insertBefore(q.measureRight=vf(a.display.measure),q.nextSibling)}}var r=k.textClass?k.textClass+" "+(c.textClass||""):c.textClass;return r&&(k.pre.className=r),Ne(a,"renderLine",a,c,k.pre),k}function Zd(a,b,c,d,e,f){if(b){if(Yd.test(b))for(var g=document.createDocumentFragment(),h=0;;){Yd.lastIndex=h;var i=Yd.exec(b),j=i?i.index-h:b.length-h;if(j&&(g.appendChild(document.createTextNode(b.slice(h,h+j))),a.col+=j),!i)break;if(h+=j+1," "==i[0]){var k=a.cm.options.tabSize,l=k-a.col%k;g.appendChild(lf("span",$e(l),"cm-tab")),a.col+=l}else{var m=lf("span","\u2022","cm-invalidchar");m.title="\\u"+i[0].charCodeAt(0).toString(16),g.appendChild(m),a.col+=1}}else{a.col+=b.length;var g=document.createTextNode(b)}if(c||d||e||a.measure){var n=c||"";d&&(n+=d),e&&(n+=e);var m=lf("span",[g],n);return f&&(m.title=f),a.pre.appendChild(m)}a.pre.appendChild(g)}}function $d(a,c,d,e,f){for(var g=a.cm.options.lineWrapping,h=0;h<c.length;++h){var i=c.charAt(h),j=0==h;i>="\ud800"&&"\udbff">i&&h<c.length-1?(i=c.slice(h,h+2),++h):h&&g&&rf(c,h)&&a.pre.appendChild(lf("wbr"));var k=a.measure[a.pos],l=a.measure[a.pos]=Zd(a,i,d,j&&e,h==c.length-1&&f);k&&(l.leftSide=k.leftSide||k),b&&g&&" "==i&&h&&!/\s/.test(c.charAt(h-1))&&h<c.length-1&&!/\s/.test(c.charAt(h+1))&&(l.style.whiteSpace="normal"),a.pos+=i.length}c.length&&(a.measuredSomething=!0)}function _d(a){function b(a){for(var b=" ",c=0;c<a.length-2;++c)b+=c%2?" ":"\xa0";return b+=" "}return function(c,d,e,f,g,h){return a(c,d.replace(/ {3,}/g,b),e,f,g,h)}}function ae(a,b,c,d){var e=!d&&c.replacedWith;if(e&&(a.copyWidgets&&(e=e.cloneNode(!0)),a.pre.appendChild(e),a.measure)){if(b)a.measure[a.pos]=e;else{var f=vf(a.cm.display.measure);if("bookmark"!=c.type||c.insertLeft){if(a.measure[a.pos])return;a.measure[a.pos]=a.pre.insertBefore(f,e)}else a.measure[a.pos]=a.pre.appendChild(f)}a.measuredSomething=!0}a.pos+=b}function be(a,b,c){var d=a.markedSpans,e=a.text,f=0;if(d)for(var k,m,n,o,p,q,h=e.length,i=0,g=1,j="",l=0;;){if(l==i){m=n=o=p="",q=null,l=1/0;for(var r=[],s=0;s<d.length;++s){var t=d[s],u=t.marker;t.from<=i&&(null==t.to||t.to>i)?(null!=t.to&&l>t.to&&(l=t.to,n=""),u.className&&(m+=" "+u.className),u.startStyle&&t.from==i&&(o+=" "+u.startStyle),u.endStyle&&t.to==l&&(n+=" "+u.endStyle),u.title&&!p&&(p=u.title),u.collapsed&&(!q||q.marker.size<u.size)&&(q=t)):t.from>i&&l>t.from&&(l=t.from),"bookmark"==u.type&&t.from==i&&u.replacedWith&&r.push(u)}if(q&&(q.from||0)==i&&(ae(b,(null==q.to?h:q.to)-i,q.marker,null==q.from),null==q.to))return q.marker.find();if(!q&&r.length)for(var s=0;s<r.length;++s)ae(b,0,r[s])
+}if(i>=h)break;for(var v=Math.min(h,l);;){if(j){var w=i+j.length;if(!q){var x=w>v?j.slice(0,v-i):j;b.addToken(b,x,k?k+m:m,o,i+x.length==l?n:"",p)}if(w>=v){j=j.slice(v-i),i=v;break}i=w,o=""}j=e.slice(f,f=c[g++]),k=Wd(c[g++],b)}}else for(var g=1;g<c.length;g+=2)b.addToken(b,e.slice(f,f=c[g]),Wd(c[g+1],b))}function ce(a,b,c,d,e){function f(a){return c?c[a]:null}function g(a,c,d){Pd(a,c,d,e),Qe(a,"change",a,b)}var h=b.from,i=b.to,j=b.text,k=le(a,h.line),l=le(a,i.line),m=_e(j),n=f(j.length-1),o=i.line-h.line;if(0==h.ch&&0==i.ch&&""==m){for(var p=0,q=j.length-1,r=[];q>p;++p)r.push(new Od(j[p],f(p),e));g(l,l.text,n),o&&a.remove(h.line,o),r.length&&a.insert(h.line,r)}else if(k==l)if(1==j.length)g(k,k.text.slice(0,h.ch)+m+k.text.slice(i.ch),n);else{for(var r=[],p=1,q=j.length-1;q>p;++p)r.push(new Od(j[p],f(p),e));r.push(new Od(m+k.text.slice(i.ch),n,e)),g(k,k.text.slice(0,h.ch)+j[0],f(0)),a.insert(h.line+1,r)}else if(1==j.length)g(k,k.text.slice(0,h.ch)+j[0]+l.text.slice(i.ch),f(0)),a.remove(h.line+1,o);else{g(k,k.text.slice(0,h.ch)+j[0],f(0)),g(l,m+l.text.slice(i.ch),n);for(var p=1,q=j.length-1,r=[];q>p;++p)r.push(new Od(j[p],f(p),e));o>1&&a.remove(h.line+1,o-1),a.insert(h.line+1,r)}Qe(a,"change",a,b),Lc(a,d.anchor,d.head,null,!0)}function de(a){this.lines=a,this.parent=null;for(var b=0,c=a.length,d=0;c>b;++b)a[b].parent=this,d+=a[b].height;this.height=d}function ee(a){this.children=a;for(var b=0,c=0,d=0,e=a.length;e>d;++d){var f=a[d];b+=f.chunkSize(),c+=f.height,f.parent=this}this.size=b,this.height=c,this.parent=null}function je(a,b,c){function d(a,e,f){if(a.linked)for(var g=0;g<a.linked.length;++g){var h=a.linked[g];if(h.doc!=e){var i=f&&h.sharedHist;(!c||i)&&(b(h.doc,i),d(h.doc,a,i))}}}d(a,null,!0)}function ke(a,b){if(b.cm)throw new Error("This document is already in use.");a.doc=b,b.cm=a,C(a),z(a),a.options.lineWrapping||I(a),a.options.mode=b.modeOption,Ib(a)}function le(a,b){for(b-=a.first;!a.lines;)for(var c=0;;++c){var d=a.children[c],e=d.chunkSize();if(e>b){a=d;break}b-=e}return a.lines[b]}function me(a,b,c){var d=[],e=b.line;return a.iter(b.line,c.line+1,function(a){var f=a.text;e==c.line&&(f=f.slice(0,c.ch)),e==b.line&&(f=f.slice(b.ch)),d.push(f),++e}),d}function ne(a,b,c){var d=[];return a.iter(b,c,function(a){d.push(a.text)}),d}function oe(a,b){for(var c=b-a.height,d=a;d;d=d.parent)d.height+=c}function pe(a){if(null==a.parent)return null;for(var b=a.parent,c=bf(b.lines,a),d=b.parent;d;b=d,d=d.parent)for(var e=0;d.children[e]!=b;++e)c+=d.children[e].chunkSize();return c+b.first}function qe(a,b){var c=a.first;a:do{for(var d=0,e=a.children.length;e>d;++d){var f=a.children[d],g=f.height;if(g>b){a=f;continue a}b-=g,c+=f.chunkSize()}return c}while(!a.lines);for(var d=0,e=a.lines.length;e>d;++d){var h=a.lines[d],i=h.height;if(i>b)break;b-=i}return c+d}function re(a,b){b=Fd(a.doc,b);for(var c=0,d=b.parent,e=0;e<d.lines.length;++e){var f=d.lines[e];if(f==b)break;c+=f.height}for(var g=d.parent;g;d=g,g=d.parent)for(var e=0;e<g.children.length;++e){var h=g.children[e];if(h==d)break;c+=h.height}return c}function se(a){var b=a.order;return null==b&&(b=a.order=Nf(a.text)),b}function te(a){return{done:[],undone:[],undoDepth:1/0,lastTime:0,lastOp:null,lastOrigin:null,generation:a||1,maxGeneration:a||1}}function ue(a,b,c,d){var e=b["spans_"+a.id],f=0;a.iter(Math.max(a.first,c),Math.min(a.first+a.size,d),function(c){c.markedSpans&&((e||(e=b["spans_"+a.id]={}))[f]=c.markedSpans),++f})}function ve(a,b){var c={line:b.from.line,ch:b.from.ch},d={from:c,to:qc(b),text:me(a,b.from,b.to)};return ue(a,d,b.from.line,b.to.line+1),je(a,function(a){ue(a,d,b.from.line,b.to.line+1)},!0),d}function we(a,b,c,d){var e=a.history;e.undone.length=0;var f=+new Date,g=_e(e.done);if(g&&(e.lastOp==d||e.lastOrigin==b.origin&&b.origin&&("+"==b.origin.charAt(0)&&a.cm&&e.lastTime>f-a.cm.options.historyEventDelay||"*"==b.origin.charAt(0)))){var h=_e(g.changes);Cc(b.from,b.to)&&Cc(b.from,h.to)?h.to=qc(b):g.changes.push(ve(a,b)),g.anchorAfter=c.anchor,g.headAfter=c.head}else for(g={changes:[ve(a,b)],generation:e.generation,anchorBefore:a.sel.anchor,headBefore:a.sel.head,anchorAfter:c.anchor,headAfter:c.head},e.done.push(g),e.generation=++e.maxGeneration;e.done.length>e.undoDepth;)e.done.shift();e.lastTime=f,e.lastOp=d,e.lastOrigin=b.origin}function xe(a){if(!a)return null;for(var c,b=0;b<a.length;++b)a[b].marker.explicitlyCleared?c||(c=a.slice(0,b)):c&&c.push(a[b]);return c?c.length?c:null:a}function ye(a,b){var c=b["spans_"+a.id];if(!c)return null;for(var d=0,e=[];d<b.text.length;++d)e.push(xe(c[d]));return e}function ze(a,b){for(var c=0,d=[];c<a.length;++c){var e=a[c],f=e.changes,g=[];d.push({changes:g,anchorBefore:e.anchorBefore,headBefore:e.headBefore,anchorAfter:e.anchorAfter,headAfter:e.headAfter});for(var h=0;h<f.length;++h){var j,i=f[h];if(g.push({from:i.from,to:i.to,text:i.text}),b)for(var k in i)(j=k.match(/^spans_(\d+)$/))&&bf(b,Number(j[1]))>-1&&(_e(g)[k]=i[k],delete i[k])}}return d}function Ae(a,b,c,d){c<a.line?a.line+=d:b<a.line&&(a.line=b,a.ch=0)}function Be(a,b,c,d){for(var e=0;e<a.length;++e){for(var f=a[e],g=!0,h=0;h<f.changes.length;++h){var i=f.changes[h];if(f.copied||(i.from=Ec(i.from),i.to=Ec(i.to)),c<i.from.line)i.from.line+=d,i.to.line+=d;else if(b<=i.to.line){g=!1;break}}f.copied||(f.anchorBefore=Ec(f.anchorBefore),f.headBefore=Ec(f.headBefore),f.anchorAfter=Ec(f.anchorAfter),f.readAfter=Ec(f.headAfter),f.copied=!0),g?(Ae(f.anchorBefore),Ae(f.headBefore),Ae(f.anchorAfter),Ae(f.headAfter)):(a.splice(0,e+1),e=0)}}function Ce(a,b){var c=b.from.line,d=b.to.line,e=b.text.length-(d-c)-1;Be(a.done,c,d,e),Be(a.undone,c,d,e)}function De(){Ie(this)}function Ee(a){return a.stop||(a.stop=De),a}function Fe(a){a.preventDefault?a.preventDefault():a.returnValue=!1}function Ge(a){a.stopPropagation?a.stopPropagation():a.cancelBubble=!0}function He(a){return null!=a.defaultPrevented?a.defaultPrevented:0==a.returnValue}function Ie(a){Fe(a),Ge(a)}function Je(a){return a.target||a.srcElement}function Ke(a){var b=a.which;return null==b&&(1&a.button?b=1:2&a.button?b=3:4&a.button&&(b=2)),q&&a.ctrlKey&&1==b&&(b=3),b}function Le(a,b,c){if(a.addEventListener)a.addEventListener(b,c,!1);else if(a.attachEvent)a.attachEvent("on"+b,c);else{var d=a._handlers||(a._handlers={}),e=d[b]||(d[b]=[]);e.push(c)}}function Me(a,b,c){if(a.removeEventListener)a.removeEventListener(b,c,!1);else if(a.detachEvent)a.detachEvent("on"+b,c);else{var d=a._handlers&&a._handlers[b];if(!d)return;for(var e=0;e<d.length;++e)if(d[e]==c){d.splice(e,1);break}}}function Ne(a,b){var c=a._handlers&&a._handlers[b];if(c)for(var d=Array.prototype.slice.call(arguments,2),e=0;e<c.length;++e)c[e].apply(null,d)}function Qe(a,b){function e(a){return function(){a.apply(null,d)}}var c=a._handlers&&a._handlers[b];if(c){var d=Array.prototype.slice.call(arguments,2);Oe||(++Pe,Oe=[],setTimeout(Se,0));for(var f=0;f<c.length;++f)Oe.push(e(c[f]))}}function Re(a,b,c){return Ne(a,c||b.type,a,b),He(b)||b.codemirrorIgnore}function Se(){--Pe;var a=Oe;Oe=null;for(var b=0;b<a.length;++b)a[b]()}function Te(a,b){var c=a._handlers&&a._handlers[b];return c&&c.length>0}function Ue(a){a.prototype.on=function(a,b){Le(this,a,b)},a.prototype.off=function(a,b){Me(this,a,b)}}function Xe(){this.id=null}function Ye(a,b,c,d,e){null==b&&(b=a.search(/[^\s\u00a0]/),-1==b&&(b=a.length));for(var f=d||0,g=e||0;b>f;++f)" "==a.charAt(f)?g+=c-g%c:++g;return g}function $e(a){for(;Ze.length<=a;)Ze.push(_e(Ze)+" ");return Ze[a]}function _e(a){return a[a.length-1]}function af(a){if(o)a.selectionStart=0,a.selectionEnd=a.value.length;else try{a.select()}catch(b){}}function bf(a,b){if(a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;++c)if(a[c]==b)return c;return-1}function cf(a,b){function c(){}c.prototype=a;var d=new c;return b&&df(b,d),d}function df(a,b){b||(b={});for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function ef(a){for(var b=[],c=0;a>c;++c)b.push(void 0);return b}function ff(a){var b=Array.prototype.slice.call(arguments,1);return function(){return a.apply(null,b)}}function hf(a){return/\w/.test(a)||a>"\x80"&&(a.toUpperCase()!=a.toLowerCase()||gf.test(a))}function jf(a){for(var b in a)if(a.hasOwnProperty(b)&&a[b])return!1;return!0}function lf(a,b,c,d){var e=document.createElement(a);if(c&&(e.className=c),d&&(e.style.cssText=d),"string"==typeof b)of(e,b);else if(b)for(var f=0;f<b.length;++f)e.appendChild(b[f]);return e}function mf(a){for(var b=a.childNodes.length;b>0;--b)a.removeChild(a.firstChild);return a}function nf(a,b){return mf(a).appendChild(b)}function of(a,b){d?(a.innerHTML="",a.appendChild(document.createTextNode(b))):a.textContent=b}function pf(a){return a.getBoundingClientRect()}function rf(){return!1}function tf(a){if(null!=sf)return sf;var b=lf("div",null,null,"width: 50px; height: 50px; overflow-x: scroll");return nf(a,b),b.offsetWidth&&(sf=b.offsetHeight-b.clientHeight),sf||0}function vf(a){if(null==uf){var b=lf("span","\u200b");nf(a,lf("span",[b,document.createTextNode("x")])),0!=a.firstChild.offsetHeight&&(uf=b.offsetWidth<=1&&b.offsetHeight>2&&!c)}return uf?lf("span","\u200b"):lf("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px")}function Af(a,b,c,d){if(!a)return d(b,c,"ltr");for(var e=!1,f=0;f<a.length;++f){var g=a[f];(g.from<c&&g.to>b||b==c&&g.to==b)&&(d(Math.max(g.from,b),Math.min(g.to,c),1==g.level?"rtl":"ltr"),e=!0)}e||d(b,c,"ltr")}function Bf(a){return a.level%2?a.to:a.from}function Cf(a){return a.level%2?a.from:a.to}function Df(a){var b=se(a);return b?Bf(b[0]):0}function Ef(a){var b=se(a);return b?Cf(_e(b)):a.text.length}function Ff(a,b){var c=le(a.doc,b),d=Fd(a.doc,c);d!=c&&(b=pe(d));var e=se(d),f=e?e[0].level%2?Ef(d):Df(d):0;return Bc(b,f)}function Gf(a,b){for(var c,d;c=Ed(d=le(a.doc,b));)b=c.find().to.line;var e=se(d),f=e?e[0].level%2?Df(d):Ef(d):d.text.length;return Bc(b,f)}function Hf(a,b,c){var d=a[0].level;return b==d?!0:c==d?!1:c>b}function Jf(a,b){for(var d,c=0;c<a.length;++c){var e=a[c];if(e.from<b&&e.to>b)return If=null,c;if(e.from==b||e.to==b){if(null!=d)return Hf(a,e.level,a[d].level)?(If=d,c):(If=c,d);d=c}}return If=null,d}function Kf(a,b,c,d){if(!d)return b+c;do b+=c;while(b>0&&kf.test(a.text.charAt(b)));return b}function Lf(a,b,c,d){var e=se(a);if(!e)return Mf(a,b,c,d);for(var f=Jf(e,b),g=e[f],h=Kf(a,b,g.level%2?-c:c,d);;){if(h>g.from&&h<g.to)return h;if(h==g.from||h==g.to)return Jf(e,h)==f?h:(g=e[f+=c],c>0==g.level%2?g.to:g.from);if(g=e[f+=c],!g)return null;h=c>0==g.level%2?Kf(a,g.to,-1,d):Kf(a,g.from,1,d)}}function Mf(a,b,c,d){var e=b+c;if(d)for(;e>0&&kf.test(a.text.charAt(e));)e+=c;return 0>e||e>a.text.length?null:e}var a=/gecko\/\d/i.test(navigator.userAgent),b=/MSIE \d/.test(navigator.userAgent),c=b&&(null==document.documentMode||document.documentMode<8),d=b&&(null==document.documentMode||document.documentMode<9),e=/Trident\/([7-9]|\d{2,})\./,f=/WebKit\//.test(navigator.userAgent),g=f&&/Qt\/\d+\.\d+/.test(navigator.userAgent),h=/Chrome\//.test(navigator.userAgent),i=/Opera\//.test(navigator.userAgent),j=/Apple Computer/.test(navigator.vendor),k=/KHTML\//.test(navigator.userAgent),l=/Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent),m=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent),n=/PhantomJS/.test(navigator.userAgent),o=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),p=o||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent),q=o||/Mac/.test(navigator.platform),r=/win/i.test(navigator.platform),s=i&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);s&&(s=Number(s[1])),s&&s>=15&&(i=!1,f=!0);var zb,Sb,Tb,t=q&&(g||i&&(null==s||12.11>s)),u=a||b&&!d,v=!1,w=!1,Cb=0,Yb=0,bc=0,cc=null;b?cc=-.53:a?cc=15:h?cc=-.7:j&&(cc=-1/3);var gc,oc,jc=null,qc=x.changeEnd=function(a){return a.text?Bc(a.from.line+a.text.length-1,_e(a.text).length+(1==a.text.length?a.from.ch:0)):a.to};x.Pos=Bc,x.prototype={constructor:x,focus:function(){window.focus(),Nb(this),mc(this),Kb(this)},setOption:function(a,b){var c=this.options,d=c[a];(c[a]!=b||"mode"==a)&&(c[a]=b,$c.hasOwnProperty(a)&&Fb(this,$c[a])(this,b,d))},getOption:function(a){return this.options[a]},getDoc:function(){return this.doc},addKeyMap:function(a,b){this.state.keyMaps[b?"push":"unshift"](a)},removeKeyMap:function(a){for(var b=this.state.keyMaps,c=0;c<b.length;++c)if(b[c]==a||"string"!=typeof b[c]&&b[c].name==a)return b.splice(c,1),!0},addOverlay:Fb(null,function(a,b){var c=a.token?a:x.getMode(this.options,a);if(c.startState)throw new Error("Overlays may not be stateful.");this.state.overlays.push({mode:c,modeSpec:a,opaque:b&&b.opaque}),this.state.modeGen++,Ib(this)}),removeOverlay:Fb(null,function(a){for(var b=this.state.overlays,c=0;c<b.length;++c){var d=b[c].modeSpec;if(d==a||"string"==typeof a&&d.name==a)return b.splice(c,1),this.state.modeGen++,Ib(this),void 0}}),indentLine:Fb(null,function(a,b,c){"string"!=typeof b&&"number"!=typeof b&&(b=null==b?this.options.smartIndent?"smart":"prev":b?"add":"subtract"),Ic(this.doc,a)&&Uc(this,a,b,c)}),indentSelection:Fb(null,function(a){var b=this.doc.sel;if(Cc(b.from,b.to))return Uc(this,b.from.line,a);for(var c=b.to.line-(b.to.ch?0:1),d=b.from.line;c>=d;++d)Uc(this,d,a)}),getTokenAt:function(a,b){var c=this.doc;a=Gc(c,a);for(var d=eb(this,a.line,b),e=this.doc.mode,f=le(c,a.line),g=new pd(f.text,this.options.tabSize);g.pos<a.ch&&!g.eol();){g.start=g.pos;var h=e.token(g,d)}return{start:g.start,end:g.pos,string:g.current(),className:h||null,type:h||null,state:d}},getTokenTypeAt:function(a){a=Gc(this.doc,a);var b=Td(this,le(this.doc,a.line)),c=0,d=(b.length-1)/2,e=a.ch;if(0==e)return b[2];for(;;){var f=c+d>>1;if((f?b[2*f-1]:0)>=e)d=f;else{if(!(b[2*f+1]<e))return b[2*f+2];c=f+1}}},getModeAt:function(a){var b=this.doc.mode;return b.innerMode?x.innerMode(b,this.getTokenAt(a).state).mode:b},getHelper:function(a,b){if(gd.hasOwnProperty(b)){var c=gd[b],d=this.getModeAt(a);return d[b]&&c[d[b]]||d.helperType&&c[d.helperType]||c[d.name]}},getStateAfter:function(a,b){var c=this.doc;return a=Fc(c,null==a?c.first+c.size-1:a),eb(this,a+1,b)},cursorCoords:function(a,b){var c,d=this.doc.sel;return c=null==a?d.head:"object"==typeof a?Gc(this.doc,a):a?d.from:d.to,vb(this,c,b||"page")},charCoords:function(a,b){return ub(this,Gc(this.doc,a),b||"page")},coordsChar:function(a,b){return a=tb(this,a,b||"page"),xb(this,a.left,a.top)},lineAtHeight:function(a,b){return a=tb(this,{top:a,left:0},b||"page").top,qe(this.doc,a+this.display.viewOffset)},heightAtLine:function(a,b){var c=!1,d=this.doc.first+this.doc.size-1;a<this.doc.first?a=this.doc.first:a>d&&(a=d,c=!0);var e=le(this.doc,a);return sb(this,le(this.doc,a),{top:0,left:0},b||"page").top+(c?e.height:0)},defaultTextHeight:function(){return Ab(this.display)},defaultCharWidth:function(){return Bb(this.display)},setGutterMarker:Fb(null,function(a,b,c){return Vc(this,a,function(a){var d=a.gutterMarkers||(a.gutterMarkers={});return d[b]=c,!c&&jf(d)&&(a.gutterMarkers=null),!0})}),clearGutter:Fb(null,function(a){var b=this,c=b.doc,d=c.first;c.iter(function(c){c.gutterMarkers&&c.gutterMarkers[a]&&(c.gutterMarkers[a]=null,Ib(b,d,d+1),jf(c.gutterMarkers)&&(c.gutterMarkers=null)),++d})}),addLineClass:Fb(null,function(a,b,c){return Vc(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass";if(a[d]){if(new RegExp("(?:^|\\s)"+c+"(?:$|\\s)").test(a[d]))return!1;a[d]+=" "+c}else a[d]=c;return!0})}),removeLineClass:Fb(null,function(a,b,c){return Vc(this,a,function(a){var d="text"==b?"textClass":"background"==b?"bgClass":"wrapClass",e=a[d];if(!e)return!1;if(null==c)a[d]=null;else{var f=e.match(new RegExp("(?:^|\\s+)"+c+"(?:$|\\s+)"));if(!f)return!1;var g=f.index+f[0].length;a[d]=e.slice(0,f.index)+(f.index&&g!=e.length?" ":"")+e.slice(g)||null}return!0})}),addLineWidget:Fb(null,function(a,b,c){return Nd(this,a,b,c)}),removeLineWidget:function(a){a.clear()},lineInfo:function(a){if("number"==typeof a){if(!Ic(this.doc,a))return null;var b=a;if(a=le(this.doc,a),!a)return null}else{var b=pe(a);if(null==b)return null}return{line:b,handle:a,text:a.text,gutterMarkers:a.gutterMarkers,textClass:a.textClass,bgClass:a.bgClass,wrapClass:a.wrapClass,widgets:a.widgets}},getViewport:function(){return{from:this.display.showingFrom,to:this.display.showingTo}},addWidget:function(a,b,c,d,e){var f=this.display;a=vb(this,Gc(this.doc,a));var g=a.bottom,h=a.left;if(b.style.position="absolute",f.sizer.appendChild(b),"over"==d)g=a.top;else if("above"==d||"near"==d){var i=Math.max(f.wrapper.clientHeight,this.doc.height),j=Math.max(f.sizer.clientWidth,f.lineSpace.clientWidth);("above"==d||a.bottom+b.offsetHeight>i)&&a.top>b.offsetHeight?g=a.top-b.offsetHeight:a.bottom+b.offsetHeight<=i&&(g=a.bottom),h+b.offsetWidth>j&&(h=j-b.offsetWidth)}b.style.top=g+"px",b.style.left=b.style.right="","right"==e?(h=f.sizer.clientWidth-b.offsetWidth,b.style.right="0px"):("left"==e?h=0:"middle"==e&&(h=(f.sizer.clientWidth-b.offsetWidth)/2),b.style.left=h+"px"),c&&Qc(this,h,g,h+b.offsetWidth,g+b.offsetHeight)},triggerOnKeyDown:Fb(null,kc),execCommand:function(a){return jd[a](this)},findPosH:function(a,b,c,d){var e=1;0>b&&(e=-1,b=-b);for(var f=0,g=Gc(this.doc,a);b>f&&(g=Wc(this.doc,g,e,c,d),!g.hitSide);++f);return g},moveH:Fb(null,function(a,b){var d,c=this.doc.sel;d=c.shift||c.extend||Cc(c.from,c.to)?Wc(this.doc,c.head,a,b,this.options.rtlMoveVisually):0>a?c.from:c.to,Jc(this.doc,d,d,a)}),deleteH:Fb(null,function(a,b){var c=this.doc.sel;Cc(c.from,c.to)?Ac(this.doc,"",c.from,Wc(this.doc,c.head,a,b,!1),"+delete"):Ac(this.doc,"",c.from,c.to,"+delete"),this.curOp.userSelChange=!0}),findPosV:function(a,b,c,d){var e=1,f=d;0>b&&(e=-1,b=-b);for(var g=0,h=Gc(this.doc,a);b>g;++g){var i=vb(this,h,"div");if(null==f?f=i.left:i.left=f,h=Xc(this,i,e,c),h.hitSide)break}return h},moveV:Fb(null,function(a,b){var c=this.doc.sel,d=vb(this,c.head,"div");null!=c.goalColumn&&(d.left=c.goalColumn);var e=Xc(this,d,a,b);"page"==b&&Tc(this,0,ub(this,e,"div").top-d.top),Jc(this.doc,e,e,a),c.goalColumn=d.left}),toggleOverwrite:function(a){(null==a||a!=this.state.overwrite)&&((this.state.overwrite=!this.state.overwrite)?this.display.cursor.className+=" CodeMirror-overwrite":this.display.cursor.className=this.display.cursor.className.replace(" CodeMirror-overwrite",""))},hasFocus:function(){return this.state.focused},scrollTo:Fb(null,function(a,b){Sc(this,a,b)}),getScrollInfo:function(){var a=this.display.scroller,b=Ve;return{left:a.scrollLeft,top:a.scrollTop,height:a.scrollHeight-b,width:a.scrollWidth-b,clientHeight:a.clientHeight-b,clientWidth:a.clientWidth-b}},scrollIntoView:Fb(null,function(a,b){null==a?a={from:this.doc.sel.head,to:null}:"number"==typeof a?a={from:Bc(a,0),to:null}:null==a.from&&(a={from:a,to:null}),a.to||(a.to=a.from),b||(b=0);var c=a;null!=a.from.line&&(this.curOp.scrollToPos={from:a.from,to:a.to,margin:b},c={from:vb(this,a.from),to:vb(this,a.to)});var d=Rc(this,Math.min(c.from.left,c.to.left),Math.min(c.from.top,c.to.top)-b,Math.max(c.from.right,c.to.right),Math.max(c.from.bottom,c.to.bottom)+b);Sc(this,d.scrollLeft,d.scrollTop)}),setSize:Fb(null,function(a,b){function c(a){return"number"==typeof a||/^\d+$/.test(String(a))?a+"px":a}null!=a&&(this.display.wrapper.style.width=c(a)),null!=b&&(this.display.wrapper.style.height=c(b)),this.options.lineWrapping&&(this.display.measureLineCache.length=this.display.measureLineCachePos=0),this.curOp.forceUpdate=!0}),operation:function(a){return Hb(this,a)},refresh:Fb(null,function(){var a=null==this.display.cachedTextHeight;pb(this),Sc(this,this.doc.scrollLeft,this.doc.scrollTop),Ib(this),a&&C(this)}),swapDoc:Fb(null,function(a){var b=this.doc;return b.cm=null,ke(this,a),pb(this),Mb(this,!0),Sc(this,a.scrollLeft,a.scrollTop),Qe(this,"swapDoc",this,b),b}),getInputField:function(){return this.display.input},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},Ue(x);var $c=x.optionHandlers={},_c=x.defaults={},bd=x.Init={toString:function(){return"CodeMirror.Init"}};ad("value","",function(a,b){a.setValue(b)},!0),ad("mode",null,function(a,b){a.doc.modeOption=b,z(a)},!0),ad("indentUnit",2,z,!0),ad("indentWithTabs",!1),ad("smartIndent",!0),ad("tabSize",4,function(a){z(a),pb(a),Ib(a)},!0),ad("electricChars",!0),ad("rtlMoveVisually",!r),ad("theme","default",function(a){E(a),F(a)},!0),ad("keyMap","default",D),ad("extraKeys",null),ad("onKeyEvent",null),ad("onDragEvent",null),ad("lineWrapping",!1,A,!0),ad("gutters",[],function(a){J(a.options),F(a)},!0),ad("fixedGutter",!0,function(a,b){a.display.gutters.style.left=b?P(a.display)+"px":"0",a.refresh()},!0),ad("coverGutterNextToScrollbar",!1,K,!0),ad("lineNumbers",!1,function(a){J(a.options),F(a)},!0),ad("firstLineNumber",1,F,!0),ad("lineNumberFormatter",function(a){return a},F,!0),ad("showCursorWhenSelecting",!1,Z,!0),ad("resetSelectionOnContextMenu",!0),ad("readOnly",!1,function(a,b){"nocursor"==b?(nc(a),a.display.input.blur(),a.display.disabled=!0):(a.display.disabled=!1,b||Mb(a,!0))}),ad("dragDrop",!0),ad("cursorBlinkRate",530),ad("cursorScrollMargin",0),ad("cursorHeight",1),ad("workTime",100),ad("workDelay",100),ad("flattenSpans",!0),ad("pollInterval",100),ad("undoDepth",40,function(a,b){a.doc.history.undoDepth=b}),ad("historyEventDelay",500),ad("viewportMargin",10,function(a){a.refresh()},!0),ad("maxHighlightLength",1e4,function(a){z(a),a.refresh()},!0),ad("crudeMeasuringFrom",1e4),ad("moveInputWithCursor",!0,function(a,b){b||(a.display.inputDiv.style.top=a.display.inputDiv.style.left=0)}),ad("tabindex",null,function(a,b){a.display.input.tabIndex=b||""}),ad("autofocus",null);var cd=x.modes={},dd=x.mimeModes={};x.defineMode=function(a,b){if(x.defaults.mode||"null"==a||(x.defaults.mode=a),arguments.length>2){b.dependencies=[];for(var c=2;c<arguments.length;++c)b.dependencies.push(arguments[c])}cd[a]=b},x.defineMIME=function(a,b){dd[a]=b},x.resolveMode=function(a){if("string"==typeof a&&dd.hasOwnProperty(a))a=dd[a];else if(a&&"string"==typeof a.name&&dd.hasOwnProperty(a.name)){var b=dd[a.name];a=cf(b,a),a.name=b.name}else if("string"==typeof a&&/^[\w\-]+\/[\w\-]+\+xml$/.test(a))return x.resolveMode("application/xml");return"string"==typeof a?{name:a}:a||{name:"null"}},x.getMode=function(a,b){var b=x.resolveMode(b),c=cd[b.name];if(!c)return x.getMode(a,"text/plain");var d=c(a,b);if(ed.hasOwnProperty(b.name)){var e=ed[b.name];for(var f in e)e.hasOwnProperty(f)&&(d.hasOwnProperty(f)&&(d["_"+f]=d[f]),d[f]=e[f])}return d.name=b.name,d},x.defineMode("null",function(){return{token:function(a){a.skipToEnd()}}}),x.defineMIME("text/plain","null");var ed=x.modeExtensions={};x.extendMode=function(a,b){var c=ed.hasOwnProperty(a)?ed[a]:ed[a]={};df(b,c)},x.defineExtension=function(a,b){x.prototype[a]=b},x.defineDocExtension=function(a,b){ge.prototype[a]=b},x.defineOption=ad;var fd=[];x.defineInitHook=function(a){fd.push(a)};var gd=x.helpers={};x.registerHelper=function(a,b,c){gd.hasOwnProperty(a)||(gd[a]=x[a]={}),gd[a][b]=c},x.isWordChar=hf,x.copyState=hd,x.startState=id,x.innerMode=function(a,b){for(;a.innerMode;){var c=a.innerMode(b);if(!c||c.mode==a)break;b=c.state,a=c.mode}return c||{mode:a,state:b}};var jd=x.commands={selectAll:function(a){a.setSelection(Bc(a.firstLine(),0),Bc(a.lastLine()))},killLine:function(a){var b=a.getCursor(!0),c=a.getCursor(!1),d=!Cc(b,c);d||a.getLine(b.line).length!=b.ch?a.replaceRange("",b,d?c:Bc(b.line),"+delete"):a.replaceRange("",b,Bc(b.line+1,0),"+delete")},deleteLine:function(a){var b=a.getCursor().line;a.replaceRange("",Bc(b,0),Bc(b),"+delete")},delLineLeft:function(a){var b=a.getCursor();a.replaceRange("",Bc(b.line,0),b,"+delete")},undo:function(a){a.undo()},redo:function(a){a.redo()},goDocStart:function(a){a.extendSelection(Bc(a.firstLine(),0))},goDocEnd:function(a){a.extendSelection(Bc(a.lastLine()))},goLineStart:function(a){a.extendSelection(Ff(a,a.getCursor().line))},goLineStartSmart:function(a){var b=a.getCursor(),c=Ff(a,b.line),d=a.getLineHandle(c.line),e=se(d);if(e&&0!=e[0].level)a.extendSelection(c);else{var f=Math.max(0,d.text.search(/\S/)),g=b.line==c.line&&b.ch<=f&&b.ch;a.extendSelection(Bc(c.line,g?0:f))}},goLineEnd:function(a){a.extendSelection(Gf(a,a.getCursor().line))},goLineRight:function(a){var b=a.charCoords(a.getCursor(),"div").top+5;a.extendSelection(a.coordsChar({left:a.display.lineDiv.offsetWidth+100,top:b},"div"))},goLineLeft:function(a){var b=a.charCoords(a.getCursor(),"div").top+5;a.extendSelection(a.coordsChar({left:0,top:b},"div"))},goLineUp:function(a){a.moveV(-1,"line")},goLineDown:function(a){a.moveV(1,"line")},goPageUp:function(a){a.moveV(-1,"page")},goPageDown:function(a){a.moveV(1,"page")},goCharLeft:function(a){a.moveH(-1,"char")},goCharRight:function(a){a.moveH(1,"char")},goColumnLeft:function(a){a.moveH(-1,"column")},goColumnRight:function(a){a.moveH(1,"column")},goWordLeft:function(a){a.moveH(-1,"word")},goGroupRight:function(a){a.moveH(1,"group")},goGroupLeft:function(a){a.moveH(-1,"group")},goWordRight:function(a){a.moveH(1,"word")},delCharBefore:function(a){a.deleteH(-1,"char")},delCharAfter:function(a){a.deleteH(1,"char")},delWordBefore:function(a){a.deleteH(-1,"word")},delWordAfter:function(a){a.deleteH(1,"word")},delGroupBefore:function(a){a.deleteH(-1,"group")},delGroupAfter:function(a){a.deleteH(1,"group")},indentAuto:function(a){a.indentSelection("smart")},indentMore:function(a){a.indentSelection("add")},indentLess:function(a){a.indentSelection("subtract")},insertTab:function(a){a.replaceSelection(" ","end","+input")},defaultTab:function(a){a.somethingSelected()?a.indentSelection("add"):a.replaceSelection(" ","end","+input")},transposeChars:function(a){var b=a.getCursor(),c=a.getLine(b.line);b.ch>0&&b.ch<c.length-1&&a.replaceRange(c.charAt(b.ch)+c.charAt(b.ch-1),Bc(b.line,b.ch-1),Bc(b.line,b.ch+1))},newlineAndIndent:function(a){Fb(a,function(){a.replaceSelection("\n","end","+input"),a.indentLine(a.getCursor().line,null,!0)})()},toggleOverwrite:function(a){a.toggleOverwrite()}},kd=x.keyMap={};kd.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite"},kd.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Alt-Up":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Down":"goDocEnd","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore",fallthrough:"basic"},kd.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineStart","Cmd-Right":"goLineEnd","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delLineLeft",fallthrough:["basic","emacsy"]},kd["default"]=q?kd.macDefault:kd.pcDefault,kd.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Alt-F":"goWordRight","Alt-B":"goWordLeft","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-D":"delWordAfter","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars"},x.lookupKey=md,x.isModifierKey=nd,x.keyName=od,x.fromTextArea=function(a,b){function e(){a.value=i.getValue()}if(b||(b={}),b.value=a.value,!b.tabindex&&a.tabindex&&(b.tabindex=a.tabindex),!b.placeholder&&a.placeholder&&(b.placeholder=a.placeholder),null==b.autofocus){var c=document.body;try{c=document.activeElement}catch(d){}b.autofocus=c==a||null!=a.getAttribute("autofocus")&&c==document.body}if(a.form&&(Le(a.form,"submit",e),!b.leaveSubmitMethodAlone)){var f=a.form,g=f.submit;try{var h=f.submit=function(){e(),f.submit=g,f.submit(),f.submit=h}}catch(d){}}a.style.display="none";var i=x(function(b){a.parentNode.insertBefore(b,a.nextSibling)},b);return i.save=e,i.getTextArea=function(){return a},i.toTextArea=function(){e(),a.parentNode.removeChild(i.getWrapperElement()),a.style.display="",a.form&&(Me(a.form,"submit",e),"function"==typeof a.form.submit&&(a.form.submit=g))},i},pd.prototype={eol:function(){return this.pos>=this.string.length},sol:function(){return 0==this.pos},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.pos<this.string.length?this.string.charAt(this.pos++):void 0},eat:function(a){var b=this.string.charAt(this.pos);if("string"==typeof a)var c=b==a;else var c=b&&(a.test?a.test(b):a(b));return c?(++this.pos,b):void 0},eatWhile:function(a){for(var b=this.pos;this.eat(a););return this.pos>b},eatSpace:function(){for(var a=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>a},skipToEnd:function(){this.pos=this.string.length},skipTo:function(a){var b=this.string.indexOf(a,this.pos);return b>-1?(this.pos=b,!0):void 0},backUp:function(a){this.pos-=a},column:function(){return this.lastColumnPos<this.start&&(this.lastColumnValue=Ye(this.string,this.start,this.tabSize,this.lastColumnPos,this.lastColumnValue),this.lastColumnPos=this.start),this.lastColumnValue},indentation:function(){return Ye(this.string,null,this.tabSize)},match:function(a,b,c){if("string"!=typeof a){var f=this.string.slice(this.pos).match(a);return f&&f.index>0?null:(f&&b!==!1&&(this.pos+=f[0].length),f)}var d=function(a){return c?a.toLowerCase():a},e=this.string.substr(this.pos,a.length);return d(e)==d(a)?(b!==!1&&(this.pos+=a.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)}},x.StringStream=pd,x.TextMarker=qd,Ue(qd),qd.prototype.clear=function(){if(!this.explicitlyCleared){var a=this.doc.cm,b=a&&!a.curOp;if(b&&Db(a),Te(this,"clear")){var c=this.find();c&&Qe(this,"clear",c.from,c.to)}for(var d=null,e=null,f=0;f<this.lines.length;++f){var g=this.lines[f],h=ud(g.markedSpans,this);null!=h.to&&(e=pe(g)),g.markedSpans=vd(g.markedSpans,h),null!=h.from?d=pe(g):this.collapsed&&!Gd(this.doc,g)&&a&&oe(g,Ab(a.display))}if(a&&this.collapsed&&!a.options.lineWrapping)for(var f=0;f<this.lines.length;++f){var i=Fd(a.doc,this.lines[f]),j=H(a.doc,i);j>a.display.maxLineLength&&(a.display.maxLine=i,a.display.maxLineLength=j,a.display.maxLineChanged=!0)}null!=d&&a&&Ib(a,d,e+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,a&&Mc(a)),b&&Eb(a)}},qd.prototype.find=function(){for(var a,b,c=0;c<this.lines.length;++c){var d=this.lines[c],e=ud(d.markedSpans,this);if(null!=e.from||null!=e.to){var f=pe(d);null!=e.from&&(a=Bc(f,e.from)),null!=e.to&&(b=Bc(f,e.to))}}return"bookmark"==this.type?a:a&&{from:a,to:b}},qd.prototype.changed=function(){var a=this.find(),b=this.doc.cm;if(a&&b){"bookmark"!=this.type&&(a=a.from);var c=le(this.doc,a.line);if(kb(b,c),a.line>=b.display.showingFrom&&a.line<b.display.showingTo){for(var d=b.display.lineDiv.firstChild;d;d=d.nextSibling)if(d.lineObj==c){d.offsetHeight!=c.height&&oe(c,d.offsetHeight);break}Hb(b,function(){b.curOp.selectionChanged=b.curOp.forceUpdate=b.curOp.updateMaxLine=!0})}}},qd.prototype.attachLine=function(a){if(!this.lines.length&&this.doc.cm){var b=this.doc.cm.curOp;b.maybeHiddenMarkers&&-1!=bf(b.maybeHiddenMarkers,this)||(b.maybeUnhiddenMarkers||(b.maybeUnhiddenMarkers=[])).push(this)}this.lines.push(a)},qd.prototype.detachLine=function(a){if(this.lines.splice(bf(this.lines,a),1),!this.lines.length&&this.doc.cm){var b=this.doc.cm.curOp;
+(b.maybeHiddenMarkers||(b.maybeHiddenMarkers=[])).push(this)}},x.SharedTextMarker=sd,Ue(sd),sd.prototype.clear=function(){if(!this.explicitlyCleared){this.explicitlyCleared=!0;for(var a=0;a<this.markers.length;++a)this.markers[a].clear();Qe(this,"clear")}},sd.prototype.find=function(){return this.primary.find()};var Kd=x.LineWidget=function(a,b,c){if(c)for(var d in c)c.hasOwnProperty(d)&&(this[d]=c[d]);this.cm=a,this.node=b};Ue(Kd),Kd.prototype.clear=Ld(function(){var a=this.line.widgets,b=pe(this.line);if(null!=b&&a){for(var c=0;c<a.length;++c)a[c]==this&&a.splice(c--,1);a.length||(this.line.widgets=null);var d=re(this.cm,this.line)<this.cm.doc.scrollTop;oe(this.line,Math.max(0,this.line.height-Md(this))),d&&Tc(this.cm,0,-this.height),Ib(this.cm,b,b+1)}}),Kd.prototype.changed=Ld(function(){var a=this.height;this.height=null;var b=Md(this)-a;if(b){oe(this.line,this.line.height+b);var c=pe(this.line);Ib(this.cm,c,c+1)}});var Od=x.Line=function(a,b,c){this.text=a,Jd(this,b),this.height=c?c(this):1};Ue(Od);var Vd={},Yd=/[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g;de.prototype={chunkSize:function(){return this.lines.length},removeInner:function(a,b){for(var c=a,d=a+b;d>c;++c){var e=this.lines[c];this.height-=e.height,Qd(e),Qe(e,"delete")}this.lines.splice(a,b)},collapse:function(a){a.splice.apply(a,[a.length,0].concat(this.lines))},insertInner:function(a,b,c){this.height+=c,this.lines=this.lines.slice(0,a).concat(b).concat(this.lines.slice(a));for(var d=0,e=b.length;e>d;++d)b[d].parent=this},iterN:function(a,b,c){for(var d=a+b;d>a;++a)if(c(this.lines[a]))return!0}},ee.prototype={chunkSize:function(){return this.size},removeInner:function(a,b){this.size-=b;for(var c=0;c<this.children.length;++c){var d=this.children[c],e=d.chunkSize();if(e>a){var f=Math.min(b,e-a),g=d.height;if(d.removeInner(a,f),this.height-=g-d.height,e==f&&(this.children.splice(c--,1),d.parent=null),0==(b-=f))break;a=0}else a-=e}if(this.size-b<25){var h=[];this.collapse(h),this.children=[new de(h)],this.children[0].parent=this}},collapse:function(a){for(var b=0,c=this.children.length;c>b;++b)this.children[b].collapse(a)},insertInner:function(a,b,c){this.size+=b.length,this.height+=c;for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>=a){if(f.insertInner(a,b,c),f.lines&&f.lines.length>50){for(;f.lines.length>50;){var h=f.lines.splice(f.lines.length-25,25),i=new de(h);f.height-=i.height,this.children.splice(d+1,0,i),i.parent=this}this.maybeSpill()}break}a-=g}},maybeSpill:function(){if(!(this.children.length<=10)){var a=this;do{var b=a.children.splice(a.children.length-5,5),c=new ee(b);if(a.parent){a.size-=c.size,a.height-=c.height;var e=bf(a.parent.children,a);a.parent.children.splice(e+1,0,c)}else{var d=new ee(a.children);d.parent=a,a.children=[d,c],a=d}c.parent=a.parent}while(a.children.length>10);a.parent.maybeSpill()}},iterN:function(a,b,c){for(var d=0,e=this.children.length;e>d;++d){var f=this.children[d],g=f.chunkSize();if(g>a){var h=Math.min(b,g-a);if(f.iterN(a,h,c))return!0;if(0==(b-=h))break;a=0}else a-=g}}};var fe=0,ge=x.Doc=function(a,b,c){if(!(this instanceof ge))return new ge(a,b,c);null==c&&(c=0),ee.call(this,[new de([new Od("",null)])]),this.first=c,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.history=te(),this.cleanGeneration=1,this.frontier=c;var d=Bc(c,0);this.sel={from:d,to:d,head:d,anchor:d,shift:!1,extend:!1,goalColumn:null},this.id=++fe,this.modeOption=b,"string"==typeof a&&(a=wf(a)),ce(this,{from:d,to:d,text:a},null,{head:d,anchor:d})};ge.prototype=cf(ee.prototype,{constructor:ge,iter:function(a,b,c){c?this.iterN(a-this.first,b-a,c):this.iterN(this.first,this.first+this.size,a)},insert:function(a,b){for(var c=0,d=0,e=b.length;e>d;++d)c+=b[d].height;this.insertInner(a-this.first,b,c)},remove:function(a,b){this.removeInner(a-this.first,b)},getValue:function(a){var b=ne(this,this.first,this.first+this.size);return a===!1?b:b.join(a||"\n")},setValue:function(a){var b=Bc(this.first,0),c=this.first+this.size-1;uc(this,{from:b,to:Bc(c,le(this,c).text.length),text:wf(a),origin:"setValue"},{head:b,anchor:b},!0)},replaceRange:function(a,b,c,d){b=Gc(this,b),c=c?Gc(this,c):b,Ac(this,a,b,c,d)},getRange:function(a,b,c){var d=me(this,Gc(this,a),Gc(this,b));return c===!1?d:d.join(c||"\n")},getLine:function(a){var b=this.getLineHandle(a);return b&&b.text},setLine:function(a,b){Ic(this,a)&&Ac(this,b,Bc(a,0),Gc(this,Bc(a)))},removeLine:function(a){a?Ac(this,"",Gc(this,Bc(a-1)),Gc(this,Bc(a))):Ac(this,"",Bc(0,0),Gc(this,Bc(1,0)))},getLineHandle:function(a){return Ic(this,a)?le(this,a):void 0},getLineNumber:function(a){return pe(a)},getLineHandleVisualStart:function(a){return"number"==typeof a&&(a=le(this,a)),Fd(this,a)},lineCount:function(){return this.size},firstLine:function(){return this.first},lastLine:function(){return this.first+this.size-1},clipPos:function(a){return Gc(this,a)},getCursor:function(a){var c,b=this.sel;return c=null==a||"head"==a?b.head:"anchor"==a?b.anchor:"end"==a||a===!1?b.to:b.from,Ec(c)},somethingSelected:function(){return!Cc(this.sel.head,this.sel.anchor)},setCursor:Gb(function(a,b,c){var d=Gc(this,"number"==typeof a?Bc(a,b||0):a);c?Jc(this,d):Lc(this,d,d)}),setSelection:Gb(function(a,b,c){Lc(this,Gc(this,a),Gc(this,b||a),c)}),extendSelection:Gb(function(a,b,c){Jc(this,Gc(this,a),b&&Gc(this,b),c)}),getSelection:function(a){return this.getRange(this.sel.from,this.sel.to,a)},replaceSelection:function(a,b,c){uc(this,{from:this.sel.from,to:this.sel.to,text:wf(a),origin:c},b||"around")},undo:Gb(function(){wc(this,"undo")}),redo:Gb(function(){wc(this,"redo")}),setExtending:function(a){this.sel.extend=a},historySize:function(){var a=this.history;return{undo:a.done.length,redo:a.undone.length}},clearHistory:function(){this.history=te(this.history.maxGeneration)},markClean:function(){this.cleanGeneration=this.changeGeneration()},changeGeneration:function(){return this.history.lastOp=this.history.lastOrigin=null,this.history.generation},isClean:function(a){return this.history.generation==(a||this.cleanGeneration)},getHistory:function(){return{done:ze(this.history.done),undone:ze(this.history.undone)}},setHistory:function(a){var b=this.history=te(this.history.maxGeneration);b.done=a.done.slice(0),b.undone=a.undone.slice(0)},markText:function(a,b,c){return rd(this,Gc(this,a),Gc(this,b),c,"range")},setBookmark:function(a,b){var c={replacedWith:b&&(null==b.nodeType?b.widget:b),insertLeft:b&&b.insertLeft};return a=Gc(this,a),rd(this,a,a,c,"bookmark")},findMarksAt:function(a){a=Gc(this,a);var b=[],c=le(this,a.line).markedSpans;if(c)for(var d=0;d<c.length;++d){var e=c[d];(null==e.from||e.from<=a.ch)&&(null==e.to||e.to>=a.ch)&&b.push(e.marker.parent||e.marker)}return b},getAllMarks:function(){var a=[];return this.iter(function(b){var c=b.markedSpans;if(c)for(var d=0;d<c.length;++d)null!=c[d].from&&a.push(c[d].marker)}),a},posFromIndex:function(a){var b,c=this.first;return this.iter(function(d){var e=d.text.length+1;return e>a?(b=a,!0):(a-=e,++c,void 0)}),Gc(this,Bc(c,b))},indexFromPos:function(a){a=Gc(this,a);var b=a.ch;return a.line<this.first||a.ch<0?0:(this.iter(this.first,a.line,function(a){b+=a.text.length+1}),b)},copy:function(a){var b=new ge(ne(this,this.first,this.first+this.size),this.modeOption,this.first);return b.scrollTop=this.scrollTop,b.scrollLeft=this.scrollLeft,b.sel={from:this.sel.from,to:this.sel.to,head:this.sel.head,anchor:this.sel.anchor,shift:this.sel.shift,extend:!1,goalColumn:this.sel.goalColumn},a&&(b.history.undoDepth=this.history.undoDepth,b.setHistory(this.getHistory())),b},linkedDoc:function(a){a||(a={});var b=this.first,c=this.first+this.size;null!=a.from&&a.from>b&&(b=a.from),null!=a.to&&a.to<c&&(c=a.to);var d=new ge(ne(this,b,c),a.mode||this.modeOption,b);return a.sharedHist&&(d.history=this.history),(this.linked||(this.linked=[])).push({doc:d,sharedHist:a.sharedHist}),d.linked=[{doc:this,isParent:!0,sharedHist:a.sharedHist}],d},unlinkDoc:function(a){if(a instanceof x&&(a=a.doc),this.linked)for(var b=0;b<this.linked.length;++b){var c=this.linked[b];if(c.doc==a){this.linked.splice(b,1),a.unlinkDoc(this);break}}if(a.history==this.history){var d=[a.id];je(a,function(a){d.push(a.id)},!0),a.history=te(),a.history.done=ze(this.history.done,d),a.history.undone=ze(this.history.undone,d)}},iterLinkedDocs:function(a){je(this,a)},getMode:function(){return this.mode},getEditor:function(){return this.cm}}),ge.prototype.eachLine=ge.prototype.iter;var he="iter insert remove copy getEditor".split(" ");for(var ie in ge.prototype)ge.prototype.hasOwnProperty(ie)&&bf(he,ie)<0&&(x.prototype[ie]=function(a){return function(){return a.apply(this.doc,arguments)}}(ge.prototype[ie]));Ue(ge),x.e_stop=Ie,x.e_preventDefault=Fe,x.e_stopPropagation=Ge;var Oe,Pe=0;x.on=Le,x.off=Me,x.signal=Ne;var Ve=30,We=x.Pass={toString:function(){return"CodeMirror.Pass"}};Xe.prototype={set:function(a,b){clearTimeout(this.id),this.id=setTimeout(b,a)}},x.countColumn=Ye;var Ze=[""],gf=/[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,kf=/[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/;x.replaceGetRect=function(a){pf=a};var qf=function(){if(d)return!1;var a=lf("div");return"draggable"in a||"dragDrop"in a}();a?rf=function(a,b){return 36==a.charCodeAt(b-1)&&39==a.charCodeAt(b)}:j&&!/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)?rf=function(a,b){return/\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(a.slice(b-1,b+1))}:f&&/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)?rf=function(a,b){var c=a.charCodeAt(b-1);return c>=8208&&8212>=c}:f&&(rf=function(a,b){if(b>1&&45==a.charCodeAt(b-1)){if(/\w/.test(a.charAt(b-2))&&/[^\-?\.]/.test(a.charAt(b)))return!0;if(b>2&&/[\d\.,]/.test(a.charAt(b-2))&&/[\d\.,]/.test(a.charAt(b)))return!1}return/[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(a.slice(b-1,b+1))});var sf,uf,wf=3!="\n\nb".split(/\n/).length?function(a){for(var b=0,c=[],d=a.length;d>=b;){var e=a.indexOf("\n",b);-1==e&&(e=a.length);var f=a.slice(b,"\r"==a.charAt(e-1)?e-1:e),g=f.indexOf("\r");-1!=g?(c.push(f.slice(0,g)),b+=g+1):(c.push(f),b=e+1)}return c}:function(a){return a.split(/\r\n?|\n/)};x.splitLines=wf;var xf=window.getSelection?function(a){try{return a.selectionStart!=a.selectionEnd}catch(b){return!1}}:function(a){try{var b=a.ownerDocument.selection.createRange()}catch(c){}return b&&b.parentElement()==a?0!=b.compareEndPoints("StartToEnd",b):!1},yf=function(){var a=lf("div");return"oncopy"in a?!0:(a.setAttribute("oncopy","return;"),"function"==typeof a.oncopy)}(),zf={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",91:"Mod",92:"Mod",93:"Mod",109:"-",107:"=",127:"Delete",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63276:"PageUp",63277:"PageDown",63275:"End",63273:"Home",63234:"Left",63232:"Up",63235:"Right",63233:"Down",63302:"Insert",63272:"Delete"};x.keyNames=zf,function(){for(var a=0;10>a;a++)zf[a+48]=String(a);for(var a=65;90>=a;a++)zf[a]=String.fromCharCode(a);for(var a=1;12>=a;a++)zf[a+111]=zf[a+63235]="F"+a}();var If,Nf=function(){function c(c){return 255>=c?a.charAt(c):c>=1424&&1524>=c?"R":c>=1536&&1791>=c?b.charAt(c-1536):c>=1792&&2220>=c?"r":"L"}var a="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL",b="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr",d=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,e=/[stwN]/,f=/[LRr]/,g=/[Lb1n]/,h=/[1n]/,i="L";return function(a){if(!d.test(a))return!1;for(var l,b=a.length,j=[],k=0;b>k;++k)j.push(l=c(a.charCodeAt(k)));for(var k=0,m=i;b>k;++k){var l=j[k];"m"==l?j[k]=m:m=l}for(var k=0,n=i;b>k;++k){var l=j[k];"1"==l&&"r"==n?j[k]="n":f.test(l)&&(n=l,"r"==l&&(j[k]="R"))}for(var k=1,m=j[0];b-1>k;++k){var l=j[k];"+"==l&&"1"==m&&"1"==j[k+1]?j[k]="1":","!=l||m!=j[k+1]||"1"!=m&&"n"!=m||(j[k]=m),m=l}for(var k=0;b>k;++k){var l=j[k];if(","==l)j[k]="N";else if("%"==l){for(var o=k+1;b>o&&"%"==j[o];++o);for(var p=k&&"!"==j[k-1]||b-1>o&&"1"==j[o]?"1":"N",q=k;o>q;++q)j[q]=p;k=o-1}}for(var k=0,n=i;b>k;++k){var l=j[k];"L"==n&&"1"==l?j[k]="L":f.test(l)&&(n=l)}for(var k=0;b>k;++k)if(e.test(j[k])){for(var o=k+1;b>o&&e.test(j[o]);++o);for(var r="L"==(k?j[k-1]:i),s="L"==(b-1>o?j[o]:i),p=r||s?"L":"R",q=k;o>q;++q)j[q]=p;k=o-1}for(var u,t=[],k=0;b>k;)if(g.test(j[k])){var v=k;for(++k;b>k&&g.test(j[k]);++k);t.push({from:v,to:k,level:0})}else{var w=k,x=t.length;for(++k;b>k&&"L"!=j[k];++k);for(var q=w;k>q;)if(h.test(j[q])){q>w&&t.splice(x,0,{from:w,to:q,level:1});var y=q;for(++q;k>q&&h.test(j[q]);++q);t.splice(x,0,{from:y,to:q,level:2}),w=q}else++q;k>w&&t.splice(x,0,{from:w,to:k,level:1})}return 1==t[0].level&&(u=a.match(/^\s+/))&&(t[0].from=u[0].length,t.unshift({from:0,to:u[0].length,level:0})),1==_e(t).level&&(u=a.match(/\s+$/))&&(_e(t).to-=u[0].length,t.push({from:b-u[0].length,to:b,level:0})),t[0].level!=_e(t).level&&t.push({from:b,to:b,level:t[0].level}),t}}();return x.version="3.19.1",x}(),CodeMirror.defineMode("css",function(a,b){"use strict";function l(a,b){return k=b,a}function m(a,b){var c=a.next();if(d[c]){var e=d[c](a,b);if(e!==!1)return e}if("@"==c)return a.eatWhile(/[\w\\\-]/),l("def",a.current());if("="==c)l(null,"compare");else{if(("~"==c||"|"==c)&&a.eat("="))return l(null,"compare");if('"'==c||"'"==c)return b.tokenize=n(c),b.tokenize(a,b);if("#"==c)return a.eatWhile(/[\w\\\-]/),l("atom","hash");if("!"==c)return a.match(/^\s*\w*/),l("keyword","important");if(/\d/.test(c)||"."==c&&a.eat(/\d/))return a.eatWhile(/[\w.%]/),l("number","unit");if("-"!==c)return/[,+>*\/]/.test(c)?l(null,"select-op"):"."==c&&a.match(/^-?[_a-z][_a-z0-9-]*/i)?l("qualifier","qualifier"):":"==c?l("operator",c):/[;{}\[\]\(\)]/.test(c)?l(null,c):"u"==c&&a.match("rl(")?(a.backUp(1),b.tokenize=o,l("property","variable")):(a.eatWhile(/[\w\\\-]/),l("property","variable"));if(/\d/.test(a.peek()))return a.eatWhile(/[\w.%]/),l("number","unit");if(a.match(/^[^-]+-/))return l("meta","meta")}}function n(a,b){return function(c,d){for(var f,e=!1;null!=(f=c.next())&&(f!=a||e);)e=!e&&"\\"==f;return e||(b&&c.backUp(1),d.tokenize=m),l("string","string")}}function o(a,b){return a.next(),b.tokenize=a.match(/\s*[\"\']/,!1)?m:n(")",!0),l(null,"(")}b.propertyKeywords||(b=CodeMirror.resolveMode("text/css"));var c=a.indentUnit||a.tabSize||2,d=b.hooks||{},e=b.atMediaTypes||{},f=b.atMediaFeatures||{},g=b.propertyKeywords||{},h=b.colorKeywords||{},i=b.valueKeywords||{},j=!!b.allowNested,k=null;return{startState:function(a){return{tokenize:m,baseIndent:a||0,stack:[],lastToken:null}},token:function(a,b){if(b.tokenize=b.tokenize||m,b.tokenize==m&&a.eatSpace())return null;var c=b.tokenize(a,b);c&&"string"!=typeof c&&(c=l(c[0],c[1]));var d=b.stack[b.stack.length-1];if("variable"==c)return"variable-definition"==k&&b.stack.push("propertyValue"),b.lastToken="variable-2";if("property"==c){var n=a.current().toLowerCase();"propertyValue"==d?c=i.hasOwnProperty(n)?"string-2":h.hasOwnProperty(n)?"keyword":"variable-2":"rule"==d?g.hasOwnProperty(n)||(c+=" error"):"block"==d?c=g.hasOwnProperty(n)?"property":h.hasOwnProperty(n)?"keyword":i.hasOwnProperty(n)?"string-2":"tag":d&&"@media{"!=d?"@media"==d?c=e[a.current()]?"attribute":/^(only|not)$/.test(n)?"keyword":"and"==n?"error":f.hasOwnProperty(n)?"error":"attribute error":"@mediaType"==d?c=e.hasOwnProperty(n)?"attribute":"and"==n?"operator":/^(only|not)$/.test(n)?"error":"error":"@mediaType("==d?g.hasOwnProperty(n)||(e.hasOwnProperty(n)?c="error":"and"==n?c="operator":/^(only|not)$/.test(n)?c="error":c+=" error"):c="@import"==d?"tag":"error":c="tag"}else"atom"==c?d&&"@media{"!=d&&"block"!=d?"propertyValue"==d?/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(a.current())||(c+=" error"):c="error":c="builtin":"@media"==d&&"{"==k&&(c="error");if("{"==k)if("@media"==d||"@mediaType"==d)b.stack[b.stack.length-1]="@media{";else{var o=j?"block":"rule";b.stack.push(o)}else if("}"==k)for("interpolation"==d&&(c="operator");b.stack.length;){var p=b.stack.pop();if(p.indexOf("{")>-1)break}else if("interpolation"==k)b.stack.push("interpolation");else if("@media"==k)b.stack.push("@media");else if("@import"==k)b.stack.push("@import");else if("@media"==d&&/\b(keyword|attribute)\b/.test(c))b.stack[b.stack.length-1]="@mediaType";else if("@mediaType"==d&&","==a.current())b.stack[b.stack.length-1]="@media";else if("("==k)"@media"==d||"@mediaType"==d?(b.stack[b.stack.length-1]="@mediaType",b.stack.push("@mediaType(")):b.stack.push("(");else if(")"==k)for(;b.stack.length;){var p=b.stack.pop();if(p.indexOf("(")>-1)break}else":"==k&&"property"==b.lastToken?b.stack.push("propertyValue"):"propertyValue"==d&&";"==k?b.stack.pop():"@import"==d&&";"==k&&b.stack.pop();return b.lastToken=c},indent:function(a,b){var d=a.stack.length;return/^\}/.test(b)&&(d-="propertyValue"==a.stack[d-1]?2:1),a.baseIndent+d*c},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",fold:"brace"}}),function(){function a(a){for(var b={},c=0;c<a.length;++c)b[a[c]]=!0;return b}function g(a,b){for(var d,c=!1;null!=(d=a.next());){if(c&&"/"==d){b.tokenize=null;break}c="*"==d}return["comment","comment"]}var b=a(["all","aural","braille","handheld","print","projection","screen","tty","tv","embossed"]),c=a(["width","min-width","max-width","height","min-height","max-height","device-width","min-device-width","max-device-width","device-height","min-device-height","max-device-height","aspect-ratio","min-aspect-ratio","max-aspect-ratio","device-aspect-ratio","min-device-aspect-ratio","max-device-aspect-ratio","color","min-color","max-color","color-index","min-color-index","max-color-index","monochrome","min-monochrome","max-monochrome","resolution","min-resolution","max-resolution","scan","grid"]),d=a(["align-content","align-items","align-self","alignment-adjust","alignment-baseline","anchor-point","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","appearance","azimuth","backface-visibility","background","background-attachment","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","baseline-shift","binding","bleed","bookmark-label","bookmark-level","bookmark-state","bookmark-target","border","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","clear","clip","color","color-profile","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","content","counter-increment","counter-reset","crop","cue","cue-after","cue-before","cursor","direction","display","dominant-baseline","drop-initial-after-adjust","drop-initial-after-align","drop-initial-before-adjust","drop-initial-before-align","drop-initial-size","drop-initial-value","elevation","empty-cells","fit","fit-position","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","float-offset","flow-from","flow-into","font","font-feature-settings","font-family","font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style","font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","grid-cell","grid-column","grid-column-align","grid-column-sizing","grid-column-span","grid-columns","grid-flow","grid-row","grid-row-align","grid-row-sizing","grid-row-span","grid-rows","grid-template","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","inline-box-align","justify-content","left","letter-spacing","line-break","line-height","line-stacking","line-stacking-ruby","line-stacking-shift","line-stacking-strategy","list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom","margin-left","margin-right","margin-top","marker-offset","marks","marquee-direction","marquee-loop","marquee-play-count","marquee-speed","marquee-style","max-height","max-width","min-height","min-width","move-to","nav-down","nav-index","nav-left","nav-right","nav-up","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-style","overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right","padding-top","page","page-break-after","page-break-before","page-break-inside","page-policy","pause","pause-after","pause-before","perspective","perspective-origin","pitch","pitch-range","play-during","position","presentation-level","punctuation-trim","quotes","region-break-after","region-break-before","region-break-inside","region-fragment","rendering-intent","resize","rest","rest-after","rest-before","richness","right","rotation","rotation-point","ruby-align","ruby-overhang","ruby-position","ruby-span","shape-inside","shape-outside","size","speak","speak-as","speak-header","speak-numeral","speak-punctuation","speech-rate","stress","string-set","tab-size","table-layout","target","target-name","target-new","target-position","text-align","text-align-last","text-decoration","text-decoration-color","text-decoration-line","text-decoration-skip","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-height","text-indent","text-justify","text-outline","text-overflow","text-shadow","text-size-adjust","text-space-collapse","text-transform","text-underline-position","text-wrap","top","transform","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","volume","white-space","widows","width","word-break","word-spacing","word-wrap","z-index","zoom","clip-path","clip-rule","mask","enable-background","filter","flood-color","flood-opacity","lighting-color","stop-color","stop-opacity","pointer-events","color-interpolation","color-interpolation-filters","color-profile","color-rendering","fill","fill-opacity","fill-rule","image-rendering","marker","marker-end","marker-mid","marker-start","shape-rendering","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-rendering","baseline-shift","dominant-baseline","glyph-orientation-horizontal","glyph-orientation-vertical","kerning","text-anchor","writing-mode"]),e=a(["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"]),f=a(["above","absolute","activeborder","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","contain","content","content-box","context-menu","continuous","copy","cover","crop","cross","crosshair","currentcolor","cursive","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ew-resize","expanded","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-table","inset","inside","intrinsic","invert","italic","justify","kannada","katakana","katakana-iroha","keep-all","khmer","landscape","lao","large","larger","left","level","lighter","line-through","linear","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","media-controls-background","media-current-time-display","media-fullscreen-button","media-mute-button","media-play-button","media-return-to-realtime-button","media-rewind-button","media-seek-back-button","media-seek-forward-button","media-slider","media-sliderthumb","media-time-remaining-display","media-volume-slider","media-volume-slider-container","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menulist-text","menulist-textfield","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scroll","scrollbar","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","single","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","square","square-button","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","x-large","x-small","xor","xx-large","xx-small"]);CodeMirror.defineMIME("text/css",{atMediaTypes:b,atMediaFeatures:c,propertyKeywords:d,colorKeywords:e,valueKeywords:f,hooks:{"<":function(a,b){function c(a,b){for(var d,c=0;null!=(d=a.next());){if(c>=2&&">"==d){b.tokenize=null;break}c="-"==d?c+1:0}return["comment","comment"]}return a.eat("!")?(b.tokenize=c,c(a,b)):void 0},"/":function(a,b){return a.eat("*")?(b.tokenize=g,g(a,b)):!1}},name:"css"}),CodeMirror.defineMIME("text/x-scss",{atMediaTypes:b,atMediaFeatures:c,propertyKeywords:d,colorKeywords:e,valueKeywords:f,allowNested:!0,hooks:{":":function(a){return a.match(/\s*{/)?[null,"{"]:!1},$:function(a){return a.match(/^[\w-]+/),":"==a.peek()?["variable","variable-definition"]:["variable","variable"]},",":function(a,b){return"propertyValue"==b.stack[b.stack.length-1]?["operator",";"]:void 0},"/":function(a,b){return a.eat("/")?(a.skipToEnd(),["comment","comment"]):a.eat("*")?(b.tokenize=g,g(a,b)):["operator","operator"]
+},"#":function(a){return a.eat("{")?["operator","interpolation"]:(a.eatWhile(/[\w\\\-]/),["atom","hash"])}},name:"css"})}(),CodeMirror.defineMode("less",function(a){function d(a,b){return c=b,a}function f(a,b){var f=a.next();if("@"==f)return a.eatWhile(/[\w\-]/),d("meta",a.current());if("/"==f&&a.eat("*"))return b.tokenize=h,h(a,b);if("<"==f&&a.eat("!"))return b.tokenize=i,i(a,b);if("="==f)d(null,"compare");else{if("|"==f&&a.eat("="))return d(null,"compare");if('"'==f||"'"==f)return b.tokenize=j(f),b.tokenize(a,b);if("/"==f){if(a.eat("/"))return b.tokenize=g,g(a,b);if("string"==c||"("==c)return d("string","string");if(void 0!==b.stack[b.stack.length-1])return d(null,f);if(a.eatWhile(/[\a-zA-Z0-9\-_.\s]/),/\/|\)|#/.test(a.peek()||a.eatSpace()&&")"===a.peek())||a.eol())return d("string","string")}else{if("!"==f)return a.match(/^\s*\w*/),d("keyword","important");if(/\d/.test(f))return a.eatWhile(/[\w.%]/),d("number","unit");if(/[,+<>*\/]/.test(f))return"="==a.peek()||"a"==c?d("string","string"):","===f?d(null,f):d(null,"select-op");if(/[;{}:\[\]()~\|]/.test(f)){if(":"==f)return a.eatWhile(/[a-z\\\-]/),e.test(a.current())?d("tag","tag"):":"==a.peek()?(a.next(),a.eatWhile(/[a-z\\\-]/),a.current().match(/\:\:\-(o|ms|moz|webkit)\-/)?d("string","string"):e.test(a.current().substring(1))?d("tag","tag"):d(null,f)):d(null,f);if("~"!=f)return d(null,f);if("r"==c)return d("string","string")}else{if("."==f)return"("==c?d("string","string"):(a.eatWhile(/[\a-zA-Z0-9\-_]/)," "===a.peek()&&a.eatSpace(),")"===a.peek()||":"===c?d("number","unit"):a.current().length>1&&"rule"===b.stack[b.stack.length-1]&&null===a.peek().match(/{|,|\+|\(/)?d("number","unit"):d("tag","tag"));if("#"==f)return a.eatWhile(/[A-Za-z0-9]/),4==a.current().length||7==a.current().length?null!=a.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,!1)?a.current().substring(1)!=a.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,!1)?d("atom","tag"):(a.eatSpace(),/[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(a.peek())?"select-op"===c?d("number","unit"):d("atom","tag"):"}"==a.peek()?d("number","unit"):/[a-zA-Z\\]/.test(a.peek())?d("atom","tag"):a.eol()?d("atom","tag"):d("number","unit")):(a.eatWhile(/[\w\\\-]/),d("atom",a.current())):(a.eatWhile(/[\w\\\-]/),"rule"===b.stack[b.stack.length-1]?d("atom",a.current()):d("atom",a.current()));if("&"==f)return a.eatWhile(/[\w\-]/),d(null,f);if(a.eatWhile(/[\w\\\-_%.{]/),null===a.current().match(/\\/)){if("string"==c)return"{"===b.stack[b.stack.length-1]&&":"===a.peek()?d("variable","variable"):("/"===a.peek()&&a.eatWhile(/[\w\\\-_%.{:\/]/),d(c,a.current()));if(null!=a.current().match(/(^http$|^https$)/))return a.eatWhile(/[\w\\\-_%.{:\/]/),"/"===a.peek()&&a.eatWhile(/[\w\\\-_%.{:\/]/),d("string","string");if("<"==a.peek()||">"==a.peek()||"+"==a.peek())return"("!==c||"n"!==a.current()&&"-n"!==a.current()?d("tag","tag"):d("string",a.current());if(/\(/.test(a.peek()))return"when"===a.current()?d("variable","variable"):"@media"===b.stack[b.stack.length-1]&&"and"===a.current()?d("variable",a.current()):d(null,f);if("/"==a.peek()&&void 0!==b.stack[b.stack.length-1])return"/"===a.peek()&&a.eatWhile(/[\w\\\-_%.{:\/]/),d("string",a.current());if(a.current().match(/\-\d|\-.\d/))return d("number","unit");if(/\/|[\s\)]/.test(a.peek()||a.eol()||a.eatSpace()&&"/"==a.peek())&&-1!==a.current().indexOf("."))return"{"==a.current().substring(a.current().length-1,a.current().length)?(a.backUp(1),d("tag","tag")):(a.eatSpace(),/[{<>.a-zA-Z\/]/.test(a.peek())||a.eol()?d("tag","tag"):d("string","string"));if(a.eol()||"["==a.peek()||"#"==a.peek()||"tag"==c){if("{"==a.current().substring(a.current().length-1,a.current().length))a.backUp(1);else{if("border-color"===b.stack[b.stack.length-1]||"background-position"===b.stack[b.stack.length-1]||"font-family"===b.stack[b.stack.length-1])return d(null,a.current());if("tag"===c)return d("tag","tag");if((":"===c||"unit"===c)&&"rule"===b.stack[b.stack.length-1])return d(null,a.current());if("rule"===b.stack[b.stack.length-1]&&"tag"===c)return d("string",a.current());if(";"===b.stack[b.stack.length-1]&&":"===c)return d(null,a.current());if("#"===a.peek()&&void 0!==c&&null===c.match(/\+|,|tag|select\-op|}|{|;/g))return d("string",a.current());if("variable"===c)return d(null,a.current());if("{"===b.stack[b.stack.length-1]&&"comment"===c)return d("variable",a.current());if(0===b.stack.length&&(";"===c||"comment"===c))return d("tag",a.current());if(("{"===b.stack[b.stack.length-1]||";"===c)&&"@media{"!==b.stack[b.stack.length-1])return d("variable",a.current());if("{"===b.stack[b.stack.length-2]&&";"===b.stack[b.stack.length-1])return d("variable",a.current())}return d("tag","tag")}if("compare"==c||"a"==c||"("==c)return d("string","string");if("|"==c||"-"==a.current()||"["==c)return"|"==c&&null!==a.peek().match(/\]|=|\~/)?d("number",a.current()):"|"==c?d("tag","tag"):"["==c?(a.eatWhile(/\w\-/),d("number",a.current())):d(null,f);if(":"==a.peek()||a.eatSpace()&&":"==a.peek()){a.next();var k=":"==a.peek()?!0:!1;if(k)a.backUp(1);else{var l=a.pos,m=a.current().length;a.eatWhile(/[a-z\\\-]/);var n=a.pos;if(null!=a.current().substring(m-1).match(e))return a.backUp(n-(l-1)),d("tag","tag");a.backUp(n-(l-1))}return k?d("tag","tag"):d("variable","variable")}return"font-family"===b.stack[b.stack.length-1]||"background-position"===b.stack[b.stack.length-1]||"border-color"===b.stack[b.stack.length-1]?d(null,null):null===b.stack[b.stack.length-1]&&":"===c?d(null,a.current()):/\^|\$/.test(a.current())&&null!==a.peek().match(/\~|=/)?d("string","string"):"unit"===c&&"rule"===b.stack[b.stack.length-1]?d(null,"unit"):"unit"===c&&";"===b.stack[b.stack.length-1]?d(null,"unit"):")"===c&&"rule"===b.stack[b.stack.length-1]?d(null,"unit"):c&&null!==c.match("@")&&"rule"===b.stack[b.stack.length-1]?d(null,"unit"):";"!==c&&"}"!==c&&","!==c||";"!==b.stack[b.stack.length-1]?";"===c&&void 0!==a.peek()&&null===a.peek().match(/{|./)||";"===c&&a.eatSpace()&&null===a.peek().match(/{|./)?d("variable",a.current()):"@media"===c&&"@media"===b.stack[b.stack.length-1]||"@namespace"===c?d("tag",a.current()):"{"===c&&";"===b.stack[b.stack.length-1]&&"{"===a.peek()?d("tag","tag"):"{"!==c&&":"!==c||";"!==b.stack[b.stack.length-1]?"{"===b.stack[b.stack.length-1]&&a.eatSpace()&&null===a.peek().match(/.|#/)||"select-op"===c||"rule"===b.stack[b.stack.length-1]&&","===c?d("tag","tag"):"variable"===c&&"rule"===b.stack[b.stack.length-1]?d("tag","tag"):a.eatSpace()&&"{"===a.peek()||a.eol()||"{"===a.peek()?d("tag","tag"):")"!==c||"and"!=a.current()&&"and "!=a.current()?")"!==c||"when"!=a.current()&&"when "!=a.current()?")"===c||"comment"===c||"{"===c?d("tag","tag"):a.sol()?d("tag","tag"):a.eatSpace()&&"#"===a.peek()||"#"===a.peek()?d("tag","tag"):0===b.stack.length?d("tag","tag"):";"===c&&void 0!==a.peek()&&null!==a.peek().match(/^[.|\#]/g)?d("tag","tag"):":"===c?(a.eatSpace(),d(null,a.current())):"and "===a.current()||"and"===a.current()?d("variable",a.current()):";"===c&&"{"===b.stack[b.stack.length-1]?d("variable",a.current()):"rule"===b.stack[b.stack.length-1]?d(null,a.current()):d("tag",a.current()):d("variable","variable"):d("variable","variable"):d(null,a.current()):d("tag",a.current())}if("\\"===a.current().charAt(a.current().length-1)){for(a.eat(/\'|\"|\)|\(/);a.eatWhile(/[\w\\\-_%.{]/);)a.eat(/\'|\"|\)|\(/);return d("string",a.current())}}}}}function g(a,b){return a.skipToEnd(),b.tokenize=f,d("comment","comment")}function h(a,b){for(var e,c=!1;null!=(e=a.next());){if(c&&"/"==e){b.tokenize=f;break}c="*"==e}return d("comment","comment")}function i(a,b){for(var e,c=0;null!=(e=a.next());){if(c>=2&&">"==e){b.tokenize=f;break}c="-"==e?c+1:0}return d("comment","comment")}function j(a){return function(b,c){for(var g,e=!1;null!=(g=b.next())&&(g!=a||e);)e=!e&&"\\"==g;return e||(c.tokenize=f),d("string","string")}}var c,b=a.indentUnit,e=/(^\:root$|^\:nth\-child$|^\:nth\-last\-child$|^\:nth\-of\-type$|^\:nth\-last\-of\-type$|^\:first\-child$|^\:last\-child$|^\:first\-of\-type$|^\:last\-of\-type$|^\:only\-child$|^\:only\-of\-type$|^\:empty$|^\:link|^\:visited$|^\:active$|^\:hover$|^\:focus$|^\:target$|^\:lang$|^\:enabled^\:disabled$|^\:checked$|^\:first\-line$|^\:first\-letter$|^\:before$|^\:after$|^\:not$|^\:required$|^\:invalid$)/;return{startState:function(a){return{tokenize:f,baseIndent:a||0,stack:[]}},token:function(a,b){if(a.eatSpace())return null;var e=b.tokenize(a,b),f=b.stack[b.stack.length-1];if("hash"==c&&"rule"==f?e="atom":"variable"==e&&("rule"==f?e=null:f&&"@media{"!=f||(e="when"==a.current()?"variable":/[\s,|\s\)|\s]/.test(a.peek())?"tag":c)),"rule"==f&&/^[\{\};]$/.test(c)&&b.stack.pop(),"{"==c?"@media"==f?b.stack[b.stack.length-1]="@media{":b.stack.push("{"):"}"==c?b.stack.pop():"@media"==c?b.stack.push("@media"):"font-family"===a.current()?b.stack[b.stack.length-1]="font-family":"background-position"===a.current()?b.stack[b.stack.length-1]="background-position":"border-color"===a.current()?b.stack[b.stack.length-1]="border-color":"{"==f&&"comment"!=c&&"tag"!==c?b.stack.push("rule"):":"===a.peek()&&null===a.current().match(/@|#/)&&(e=c),";"!==c||"font-family"!=b.stack[b.stack.length-1]&&"background-position"!=b.stack[b.stack.length-1]&&"border-color"!=b.stack[b.stack.length-1]){if("tag"===c&&")"===a.peek()&&null===a.current().match(/\:/))c=null,e=null;else if("variable"===c&&")"===a.peek()||"variable"===c&&a.eatSpace()&&")"===a.peek())return d(null,a.current())}else b.stack[b.stack.length-1]=a.current();return e},indent:function(a,c){var d=a.stack.length;return/^\}/.test(c)?d-="rule"===a.stack[a.stack.length-1]?2:1:"{"===a.stack[a.stack.length-2]&&(d-="rule"===a.stack[a.stack.length-1]?1:0),a.baseIndent+d*b},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",lineComment:"//"}}),CodeMirror.defineMIME("text/x-less","less"),CodeMirror.mimeModes.hasOwnProperty("text/css")||CodeMirror.defineMIME("text/css","less"); \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css-preview.js b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css-preview.js
new file mode 100644
index 00000000..15090ee3
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css-preview.js
@@ -0,0 +1,42 @@
+// Originally based on https://raw.githubusercontent.com/xwp/wp-custom-scss-demo/master/custom-scss-demo-preview.js
+/* globals jpCustomizerCssPreview */
+(function( api, $ ) {
+ if ( api.settingPreviewHandlers ) {
+ // No-op the custom_css preview handler since now handled by partial.
+ api.settingPreviewHandlers.custom_css = function() {};
+ } else {
+ parent.console.warn( 'Missing core patch that adds support for settingPreviewHandlers' );
+ }
+
+ api.selectiveRefresh.partialConstructor.custom_css = api.selectiveRefresh.Partial.extend( {
+
+ /**
+ * Refresh custom_css partial, using selective refresh if pre-processor and direct DOM manipulation if otherwise.
+ *
+ * @returns {jQuery.promise}
+ */
+ refresh: function() {
+ var partial = this,
+ preprocessor = api( 'jetpack_custom_css[preprocessor]' ).get(),
+ deferred, setting;
+
+ // Sass or Less require Partial -- so ajax call to get it from PHP.
+ // We can explicitly override for specific providers by testing if `'sass' === preprocessor`
+ if ( jpCustomizerCssPreview.preprocessors.hasOwnProperty( preprocessor ) ) {
+ return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
+ }
+
+ // No special providers, just write what we got.
+ deferred = new $.Deferred();
+ setting = api( 'custom_css[' + api.settings.theme.stylesheet + ']' );
+ _.each( partial.placements(), function( placement ) {
+ placement.container.text( setting.get() );
+ } );
+
+ deferred.resolve();
+ return deferred.promise();
+ }
+
+ } );
+
+}( wp.customize, jQuery ));
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js
new file mode 100644
index 00000000..7fef365f
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.core-4.9.js
@@ -0,0 +1,85 @@
+(function( $, customize ){
+ /**
+ * Helper function to qet a control by ID
+ * @param {string} controlId Control ID
+ * @return {object} jQuery object of the container
+ */
+ function _getControl ( controlId ) {
+ var control = customize.control.value( controlId );
+ if ( control ) {
+ return control.container;
+ }
+ return null;
+ }
+
+ /**
+ * Add some labels that the default checkbox controls don't allow.
+ * Add CSS Revisions and CSS Help links.
+ */
+ $(document).ready( function(){
+ var cssModeControl = _getControl( 'jetpack_css_mode_control' );
+ if ( cssModeControl ) {
+ cssModeControl.prepend( '<span class="customize-control-title">' + window._jp_css_settings.l10n.mode + '</span>' );
+ }
+
+ var mobileCssControl = _getControl( 'jetpack_mobile_css_control' );
+ if ( mobileCssControl ) {
+ mobileCssControl.prepend( '<span class="customize-control-title">' + window._jp_css_settings.l10n.mobile + '</span>' );
+ }
+
+ var widthControl = _getControl( 'wpcom_custom_css_content_width_control' );
+ if ( widthControl ) {
+ widthControl.append( '<span class="description">' + window._jp_css_settings.l10n.contentWidth + '<span>' );
+ widthControl.find( 'input' ).after( '<span>px</span>' );
+ }
+
+ $( '<div />', {
+ id : 'css-help-links',
+ 'class' : 'css-help'
+ }).appendTo( _getControl( 'custom_css' ) );
+
+ $( '<a />', {
+ id : 'help-link',
+ target : '_blank',
+ rel: 'noopener noreferrer',
+ href : window._jp_css_settings.cssHelpUrl,
+ text : window._jp_css_settings.l10n.css_help_title
+ }).prependTo( '#css-help-links' );
+
+ // Only show the revisions link if there are revisions
+ if ( window._jp_css_settings.areThereCssRevisions ) {
+ $( '<a />', {
+ id : 'revisions-link',
+ target : '_blank',
+ rel: 'noopener noreferrer',
+ href : window._jp_css_settings.revisionsUrl,
+ text : window._jp_css_settings.l10n.revisions
+ }).prependTo( '#css-help-links' );
+ }
+
+ customize( 'jetpack_custom_css[preprocessor]', function( preprocessorSetting ) {
+ preprocessorSetting.bind( function( curr ) {
+ var preprocessor_modes = {
+ 'default' : 'text/css',
+ less : 'text/x-less',
+ sass : 'text/x-scss'
+ },
+ new_mode = 'text/css';
+
+ if ( 'undefined' !== typeof preprocessor_modes[ curr ] ) {
+ new_mode = preprocessor_modes[ curr ];
+ }
+
+ customize.control( 'custom_css' ).deferred.codemirror.done( function ( cm ) {
+ cm.setOption( 'mode', new_mode );
+ if ( 'text/css' === new_mode ) {
+ cm.setOption( 'lint', true );
+ } else {
+ cm.setOption( 'lint', false );
+ }
+ });
+ });
+ });
+ });
+
+})( jQuery, this.wp.customize );
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.js b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.js
new file mode 100644
index 00000000..570cb8d2
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/core-customizer-css.js
@@ -0,0 +1,192 @@
+(function( wp, $, api ){
+ api.controlConstructor.jetpackCss = api.Control.extend({
+ modes: {
+ 'default': 'text/css',
+ 'less': 'text/x-less',
+ 'sass': 'text/x-scss'
+ },
+ _updating: false,
+ /**
+ * Fires when our control is ready for action. Gets everything set up.
+ * @return {null}
+ */
+ ready: function() {
+ this.opts = window._jp_css_settings;
+ // add our textarea
+ this.$input = $( '<textarea />', {
+ name: this.setting.id,
+ 'class': 'for-codemirror hidden'
+ } ).val( this.setting() );
+ this.container.append( this.$input );
+
+ // keep the textarea and the setting synced up
+ api( this.setting.id, _.bind( function( setting ){
+ var element = new api.Element( this.$input );
+ this.elements = [ element ];
+ element.sync( setting );
+ element.set( setting() );
+ }, this ) );
+
+ // should we use CodeMirror?
+ if ( this.opts.useRichEditor ) {
+ this.initCodeMirror();
+ } else {
+ this.$input.removeClass( 'hidden' );
+ }
+
+ api.bind( 'ready', _.bind( this.addLabels, this ) );
+ },
+ /**
+ * Set up our CodeMirror instance
+ * @return {null}
+ */
+ initCodeMirror: function() {
+ this.editor = window.CodeMirror.fromTextArea( this.$input.get(0), {
+ mode: this.getMode(),
+ lineNumbers: true,
+ tabSize: 2,
+ indentWithTabs: true,
+ lineWrapping: true
+ } );
+
+ this.addListeners();
+ },
+ /**
+ * Adds various listeners for CodeMirror to render and keep in sync
+ * with the textarea.
+ */
+ addListeners: function() {
+ var edited = false;
+
+ // refresh the CodeMirror instance's rendering because it's initially hidden
+ // 250ms because that's the open animation duration
+ $( '#accordion-section-custom_css > .accordion-section-title' ).click( _.bind( _.debounce( this.editor.refresh, 250 ), this.editor ) );
+ // also refresh when focusing
+ this.editor.on( 'focus', function( editor ) {
+ editor.refresh();
+ });
+
+ // when the CodeMirror instance changes, mirror to the textarea,
+ // where we have our "true" change event handler bound. This allows both to function.
+ this.editor.on( 'change', _.bind( function( editor ) {
+ this._updating = true;
+ this.$input.val( editor.getValue() ).trigger( 'change' );
+ this._updating = false;
+
+ if ( ! edited ) {
+ window.ga && window.ga( 'send', 'event', 'Customizer', 'Typed Custom CSS' );
+ edited = true;
+ }
+ }, this ) );
+
+ this.editor.on( 'focus', function() {
+ window.ga && window.ga( 'send', 'event', 'Customizer', 'Focused CSS Editor' );
+ } );
+
+ // when others update the control, update CodeMirror
+ this.setting.bind( 'change', _.bind( this.externalChange, this ) );
+ },
+ /**
+ * Get the mode of the currently active preprocessor (if any),
+ * falling back to text/css
+ * @return {string} mode for CodeMirror
+ */
+ getMode: function() {
+ var mode = api( 'jetpack_custom_css[preprocessor]' )();
+ if ( '' === mode || ! this.modes[ mode ] ) {
+ mode = 'default';
+ }
+ return this.modes[ mode ];
+ },
+ /**
+ * If another control updates our setting, re-render the CodeMirror instance
+ * @return {null}
+ */
+ externalChange: function() {
+ // only if the change wasn't internal
+ if( ! this._updating ) {
+ this.editor.setValue( this.setting() );
+ }
+ },
+ /**
+ * Callback for when the CSS panel opens to refresh the CodeMirror rendering
+ * @param {string} id The panel being opened
+ * @return {null}
+ */
+ refresh: function( id ) {
+ if ( 'accordion-section-custom_css' === id ) {
+ setTimeout( _.bind( function(){
+ this.editor.refresh();
+ }, this), 300 );
+ }
+ },
+ /**
+ * Add some labels that the default checkbox controls don't allow.
+ * Add CSS Revisions and CSS Help links.
+ */
+ addLabels: function() {
+ this.addTitle( 'jetpack_css_mode_control', this.opts.l10n.mode );
+ this.addTitle( 'jetpack_mobile_css_control', this.opts.l10n.mobile );
+ this.addDesc( 'wpcom_custom_css_content_width_control', this.opts.l10n.contentWidth );
+ var widthControl = this._getControl( 'wpcom_custom_css_content_width_control' );
+ if ( widthControl ) {
+ widthControl.find( 'input' ).after( '<span>px</span>' );
+ }
+ $( '<div />', {
+ id: 'css-help-links',
+ 'class': 'css-help'
+ }).appendTo( this.container );
+ $( '<a />', {
+ id: 'help-link',
+ target: '_blank',
+ href: this.opts.cssHelpUrl,
+ text: this.opts.l10n.css_help_title
+ }).prependTo( '#css-help-links' );
+
+ // Only show the revisions link if there are revisions
+ if ( this.opts.areThereCssRevisions ) {
+ $( '<a />', {
+ id: 'revisions-link',
+ target: '_blank',
+ href: this.opts.revisionsUrl,
+ text: this.opts.l10n.revisions
+ }).prependTo( '#css-help-links' );
+ }
+ },
+ /**
+ * Add a title to a control
+ * @param {string} controlId Control ID
+ * @param {string} title A title to add
+ */
+ addTitle: function( controlId, title ) {
+ var control = this._getControl( controlId );
+ if ( control ) {
+ control.prepend( '<span class="customize-control-title">' + title + '<span>' );
+ }
+ },
+ /**
+ * Add a description to a control
+ * @param {string} controlId Control ID
+ * @param {string} desc A description to add
+ */
+ addDesc: function( controlId, desc ) {
+ var control = this._getControl( controlId );
+ if ( control ) {
+ control.append( '<span class="description">' + desc + '<span>' );
+ }
+ },
+ /**
+ * Helper function to qet a control by ID
+ * @param {string} controlId Control ID
+ * @return {object} jQuery object of the container
+ */
+ _getControl: function( controlId ) {
+ var control = api.control.value( controlId );
+ if ( control ) {
+ return control.container;
+ }
+ return null;
+ }
+ });
+
+})( this.wp, jQuery, this.wp.customize );
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/css-editor.js b/plugins/jetpack/modules/custom-css/custom-css/js/css-editor.js
new file mode 100644
index 00000000..9b17d73f
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/css-editor.js
@@ -0,0 +1,91 @@
+/* jshint onevar: false, smarttabs: true */
+/* global postboxes, addLoadEvent */
+
+( function( $ ) {
+ var safe, win, safecssResize, safecssInit;
+
+ safecssResize = function() {
+ safe.height( win.height() - safe.offset().top - 250 );
+ };
+
+ safecssInit = function() {
+ safe = $( '#safecss' );
+ win = $( window );
+
+ postboxes.add_postbox_toggles( 'editcss' );
+ safecssResize();
+
+ // Bound on a parent to ensure that this click event executes last.
+ $( '#safecssform' ).on( 'click', '#preview', function( e ) {
+ e.preventDefault();
+
+ document.forms.safecssform.target = 'csspreview';
+ document.forms.safecssform.action.value = 'preview';
+ document.forms.safecssform.submit();
+ document.forms.safecssform.target = '';
+ document.forms.safecssform.action.value = 'save';
+ } );
+ };
+
+ window.onresize = safecssResize;
+ addLoadEvent( safecssInit );
+} )( jQuery );
+
+jQuery( function( $ ) {
+ $( '.edit-preprocessor' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#preprocessor-select' ).slideDown();
+ $( this ).hide();
+ } );
+
+ $( '.cancel-preprocessor' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#preprocessor-select' ).slideUp( function() {
+ $( '.edit-preprocessor' ).show();
+ $( '#preprocessor_choices' ).val( $( '#custom_css_preprocessor' ).val() );
+ } );
+ } );
+
+ $( '.save-preprocessor' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#preprocessor-select' ).slideUp();
+ $( '#preprocessor-display' ).text( $( '#preprocessor_choices option:selected' ).text() );
+ $( '#custom_css_preprocessor' )
+ .val( $( '#preprocessor_choices' ).val() )
+ .change();
+ $( '.edit-preprocessor' ).show();
+ } );
+
+ $( '.edit-css-mode' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#css-mode-select' ).slideDown();
+ $( this ).hide();
+ } );
+
+ $( '.cancel-css-mode' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#css-mode-select' ).slideUp( function() {
+ $( '.edit-css-mode' ).show();
+ $( 'input[name=add_to_existing_display][value=' + $( '#add_to_existing' ).val() + ']' ).attr(
+ 'checked',
+ true
+ );
+ } );
+ } );
+
+ $( '.save-css-mode' ).bind( 'click', function( e ) {
+ e.preventDefault();
+
+ $( '#css-mode-select' ).slideUp();
+ $( '#css-mode-display' ).text(
+ $( 'input[name=add_to_existing_display]:checked' ).val() === 'true' ? 'Add-on' : 'Replacement'
+ );
+ $( '#add_to_existing' ).val( $( 'input[name=add_to_existing_display]:checked' ).val() );
+ $( '.edit-css-mode' ).show();
+ } );
+} );
diff --git a/plugins/jetpack/modules/custom-css/custom-css/js/use-codemirror.js b/plugins/jetpack/modules/custom-css/custom-css/js/use-codemirror.js
new file mode 100644
index 00000000..60439308
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/js/use-codemirror.js
@@ -0,0 +1,52 @@
+/* jshint onevar: false, smarttabs: true */
+
+( function( $ ) {
+ var Jetpack_CSS = {
+ modes: {
+ default: 'text/css',
+ less: 'text/x-less',
+ sass: 'text/x-scss',
+ },
+ init: function() {
+ this.$textarea = $( '#safecss' );
+ this.editor = window.CodeMirror.fromTextArea( this.$textarea.get( 0 ), {
+ mode: this.getMode(),
+ lineNumbers: true,
+ tabSize: 2,
+ indentWithTabs: true,
+ lineWrapping: true,
+ } );
+ this.setEditorHeight();
+ },
+ addListeners: function() {
+ // nice sizing
+ $( window ).on( 'resize', _.bind( _.debounce( this.setEditorHeight, 100 ), this ) );
+ // keep textarea synced up
+ this.editor.on(
+ 'change',
+ _.bind( function( editor ) {
+ this.$textarea.val( editor.getValue() );
+ }, this )
+ );
+ // change mode
+ $( '#preprocessor_choices' ).change(
+ _.bind( function() {
+ this.editor.setOption( 'mode', this.getMode() );
+ }, this )
+ );
+ },
+ setEditorHeight: function() {
+ var height = $( 'html' ).height() - $( this.editor.getWrapperElement() ).offset().top;
+ this.editor.setSize( null, height );
+ },
+ getMode: function() {
+ var mode = $( '#preprocessor_choices' ).val();
+ if ( '' === mode || ! this.modes[ mode ] ) {
+ mode = 'default';
+ }
+ return this.modes[ mode ];
+ },
+ };
+
+ $( document ).ready( _.bind( Jetpack_CSS.init, Jetpack_CSS ) );
+} )( jQuery );
diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php
new file mode 100644
index 00000000..7d561b3d
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * CSS preprocessor registration.
+ *
+ * To add a new preprocessor (or replace an existing one), hook into the
+ * jetpack_custom_css_preprocessors filter and add an entry to the array
+ * that is passed in.
+ *
+ * Format is:
+ * $preprocessors[ UNIQUE_KEY ] => array( 'name' => 'Processor name', 'callback' => [processing function] );
+ *
+ * The callback function accepts a single string argument (non-CSS markup) and returns a string (CSS).
+ *
+ * @param array $preprocessors The list of preprocessors added thus far.
+ * @return array
+ */
+
+function jetpack_register_css_preprocessors( $preprocessors ) {
+ $preprocessors['less'] = array(
+ 'name' => 'LESS',
+ 'callback' => 'jetpack_less_css_preprocess'
+ );
+
+ $preprocessors['sass'] = array(
+ 'name' => 'Sass (SCSS Syntax)',
+ 'callback' => 'jetpack_sass_css_preprocess'
+ );
+
+ return $preprocessors;
+}
+
+add_filter( 'jetpack_custom_css_preprocessors', 'jetpack_register_css_preprocessors' );
+
+function jetpack_less_css_preprocess( $less ) {
+ require_once( dirname( __FILE__ ) . '/preprocessors/lessc.inc.php' );
+
+ $compiler = new lessc();
+
+ try {
+ return $compiler->compile( $less );
+ } catch ( Exception $e ) {
+ return $less;
+ }
+}
+
+function jetpack_sass_css_preprocess( $sass ) {
+ require_once( dirname( __FILE__ ) . '/preprocessors/scss.inc.php' );
+
+ $compiler = new scssc();
+ $compiler->setFormatter( 'scss_formatter' );
+
+ try {
+ return $compiler->compile( $sass );
+ } catch ( Exception $e ) {
+ return $sass;
+ }
+} \ No newline at end of file
diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php
new file mode 100644
index 00000000..ddaa4788
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/lessc.inc.php
@@ -0,0 +1,3768 @@
+<?php
+
+/**
+ * lessphp v0.5.0
+ * http://leafo.net/lessphp
+ *
+ * LESS CSS compiler, adapted from http://lesscss.org
+ *
+ * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
+ * Licensed under MIT or GPLv3, see LICENSE
+ */
+
+
+/**
+ * The LESS compiler and parser.
+ *
+ * Converting LESS to CSS is a three stage process. The incoming file is parsed
+ * by `lessc_parser` into a syntax tree, then it is compiled into another tree
+ * representing the CSS structure by `lessc`. The CSS tree is fed into a
+ * formatter, like `lessc_formatter` which then outputs CSS as a string.
+ *
+ * During the first compile, all values are *reduced*, which means that their
+ * types are brought to the lowest form before being dump as strings. This
+ * handles math equations, variable dereferences, and the like.
+ *
+ * The `parse` function of `lessc` is the entry point.
+ *
+ * In summary:
+ *
+ * The `lessc` class creates an instance of the parser, feeds it LESS code,
+ * then transforms the resulting tree to a CSS tree. This class also holds the
+ * evaluation context, such as all available mixins and variables at any given
+ * time.
+ *
+ * The `lessc_parser` class is only concerned with parsing its input.
+ *
+ * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
+ * handling things like indentation.
+ */
+class lessc {
+ static public $VERSION = "v0.5.0";
+
+ static public $TRUE = array("keyword", "true");
+ static public $FALSE = array("keyword", "false");
+
+ protected $libFunctions = array();
+ protected $registeredVars = array();
+ protected $preserveComments = false;
+
+ public $vPrefix = '@'; // prefix of abstract properties
+ public $mPrefix = '$'; // prefix of abstract blocks
+ public $parentSelector = '&';
+
+ public $importDisabled = false;
+ public $importDir = '';
+
+ protected $numberPrecision = null;
+
+ protected $allParsedFiles = array();
+
+ // set to the parser that generated the current line when compiling
+ // so we know how to create error messages
+ protected $sourceParser = null;
+ protected $sourceLoc = null;
+
+ static protected $nextImportId = 0; // uniquely identify imports
+
+ // attempts to find the path of an import url, returns null for css files
+ protected function findImport($url) {
+ foreach ((array)$this->importDir as $dir) {
+ $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
+ if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
+ return $file;
+ }
+ }
+
+ return null;
+ }
+
+ protected function fileExists($name) {
+ return is_file($name);
+ }
+
+ static public function compressList($items, $delim) {
+ if (!isset($items[1]) && isset($items[0])) return $items[0];
+ else return array('list', $delim, $items);
+ }
+
+ static public function preg_quote($what) {
+ return preg_quote($what, '/');
+ }
+
+ protected function tryImport($importPath, $parentBlock, $out) {
+ if ($importPath[0] == "function" && $importPath[1] == "url") {
+ $importPath = $this->flattenList($importPath[2]);
+ }
+
+ $str = $this->coerceString($importPath);
+ if ($str === null) return false;
+
+ $url = $this->compileValue($this->lib_e($str));
+
+ // don't import if it ends in css
+ if (substr_compare($url, '.css', -4, 4) === 0) return false;
+
+ $realPath = $this->findImport($url);
+
+ if ($realPath === null) return false;
+
+ if ($this->importDisabled) {
+ return array(false, "/* import disabled */");
+ }
+
+ if (isset($this->allParsedFiles[realpath($realPath)])) {
+ return array(false, null);
+ }
+
+ $this->addParsedFile($realPath);
+ $parser = $this->makeParser($realPath);
+ $root = $parser->parse(file_get_contents($realPath));
+
+ // set the parents of all the block props
+ foreach ($root->props as $prop) {
+ if ($prop[0] == "block") {
+ $prop[1]->parent = $parentBlock;
+ }
+ }
+
+ // copy mixins into scope, set their parents
+ // bring blocks from import into current block
+ // TODO: need to mark the source parser these came from this file
+ foreach ($root->children as $childName => $child) {
+ if (isset($parentBlock->children[$childName])) {
+ $parentBlock->children[$childName] = array_merge(
+ $parentBlock->children[$childName],
+ $child);
+ } else {
+ $parentBlock->children[$childName] = $child;
+ }
+ }
+
+ $pi = pathinfo($realPath);
+ $dir = $pi["dirname"];
+
+ list($top, $bottom) = $this->sortProps($root->props, true);
+ $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
+
+ return array(true, $bottom, $parser, $dir);
+ }
+
+ protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
+ $oldSourceParser = $this->sourceParser;
+
+ $oldImport = $this->importDir;
+
+ // TODO: this is because the importDir api is stupid
+ $this->importDir = (array)$this->importDir;
+ array_unshift($this->importDir, $importDir);
+
+ foreach ($props as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+
+ $this->importDir = $oldImport;
+ $this->sourceParser = $oldSourceParser;
+ }
+
+ /**
+ * Recursively compiles a block.
+ *
+ * A block is analogous to a CSS block in most cases. A single LESS document
+ * is encapsulated in a block when parsed, but it does not have parent tags
+ * so all of it's children appear on the root level when compiled.
+ *
+ * Blocks are made up of props and children.
+ *
+ * Props are property instructions, array tuples which describe an action
+ * to be taken, eg. write a property, set a variable, mixin a block.
+ *
+ * The children of a block are just all the blocks that are defined within.
+ * This is used to look up mixins when performing a mixin.
+ *
+ * Compiling the block involves pushing a fresh environment on the stack,
+ * and iterating through the props, compiling each one.
+ *
+ * See lessc::compileProp()
+ *
+ */
+ protected function compileBlock($block) {
+ switch ($block->type) {
+ case "root":
+ $this->compileRoot($block);
+ break;
+ case null:
+ $this->compileCSSBlock($block);
+ break;
+ case "media":
+ $this->compileMedia($block);
+ break;
+ case "directive":
+ $name = "@" . $block->name;
+ if (!empty($block->value)) {
+ $name .= " " . $this->compileValue($this->reduce($block->value));
+ }
+
+ $this->compileNestedBlock($block, array($name));
+ break;
+ default:
+ $this->throwError("unknown block type: $block->type\n");
+ }
+ }
+
+ protected function compileCSSBlock($block) {
+ $env = $this->pushEnv();
+
+ $selectors = $this->compileSelectors($block->tags);
+ $env->selectors = $this->multiplySelectors($selectors);
+ $out = $this->makeOutputBlock(null, $env->selectors);
+
+ $this->scope->children[] = $out;
+ $this->compileProps($block, $out);
+
+ $block->scope = $env; // mixins carry scope with them!
+ $this->popEnv();
+ }
+
+ protected function compileMedia($media) {
+ $env = $this->pushEnv($media);
+ $parentScope = $this->mediaParent($this->scope);
+
+ $query = $this->compileMediaQuery($this->multiplyMedia($env));
+
+ $this->scope = $this->makeOutputBlock($media->type, array($query));
+ $parentScope->children[] = $this->scope;
+
+ $this->compileProps($media, $this->scope);
+
+ if (count($this->scope->lines) > 0) {
+ $orphanSelelectors = $this->findClosestSelectors();
+ if (!is_null($orphanSelelectors)) {
+ $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
+ $orphan->lines = $this->scope->lines;
+ array_unshift($this->scope->children, $orphan);
+ $this->scope->lines = array();
+ }
+ }
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function mediaParent($scope) {
+ while (!empty($scope->parent)) {
+ if (!empty($scope->type) && $scope->type != "media") {
+ break;
+ }
+ $scope = $scope->parent;
+ }
+
+ return $scope;
+ }
+
+ protected function compileNestedBlock($block, $selectors) {
+ $this->pushEnv($block);
+ $this->scope = $this->makeOutputBlock($block->type, $selectors);
+ $this->scope->parent->children[] = $this->scope;
+
+ $this->compileProps($block, $this->scope);
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function compileRoot($root) {
+ $this->pushEnv();
+ $this->scope = $this->makeOutputBlock($root->type);
+ $this->compileProps($root, $this->scope);
+ $this->popEnv();
+ }
+
+ protected function compileProps($block, $out) {
+ foreach ($this->sortProps($block->props) as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+ $out->lines = $this->deduplicate($out->lines);
+ }
+
+ /**
+ * Deduplicate lines in a block. Comments are not deduplicated. If a
+ * duplicate rule is detected, the comments immediately preceding each
+ * occurence are consolidated.
+ */
+ protected function deduplicate($lines) {
+ $unique = array();
+ $comments = array();
+
+ foreach($lines as $line) {
+ if (strpos($line, '/*') === 0) {
+ $comments[] = $line;
+ continue;
+ }
+ if (!in_array($line, $unique)) {
+ $unique[] = $line;
+ }
+ array_splice($unique, array_search($line, $unique), 0, $comments);
+ $comments = array();
+ }
+ return array_merge($unique, $comments);
+ }
+
+ protected function sortProps($props, $split = false) {
+ $vars = array();
+ $imports = array();
+ $other = array();
+ $stack = array();
+
+ foreach ($props as $prop) {
+ switch ($prop[0]) {
+ case "comment":
+ $stack[] = $prop;
+ break;
+ case "assign":
+ $stack[] = $prop;
+ if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
+ $vars = array_merge($vars, $stack);
+ } else {
+ $other = array_merge($other, $stack);
+ }
+ $stack = array();
+ break;
+ case "import":
+ $id = self::$nextImportId++;
+ $prop[] = $id;
+ $stack[] = $prop;
+ $imports = array_merge($imports, $stack);
+ $other[] = array("import_mixin", $id);
+ $stack = array();
+ break;
+ default:
+ $stack[] = $prop;
+ $other = array_merge($other, $stack);
+ $stack = array();
+ break;
+ }
+ }
+ $other = array_merge($other, $stack);
+
+ if ($split) {
+ return array(array_merge($imports, $vars), $other);
+ } else {
+ return array_merge($imports, $vars, $other);
+ }
+ }
+
+ protected function compileMediaQuery($queries) {
+ $compiledQueries = array();
+ foreach ($queries as $query) {
+ $parts = array();
+ foreach ($query as $q) {
+ switch ($q[0]) {
+ case "mediaType":
+ $parts[] = implode(" ", array_slice($q, 1));
+ break;
+ case "mediaExp":
+ if (isset($q[2])) {
+ $parts[] = "($q[1]: " .
+ $this->compileValue($this->reduce($q[2])) . ")";
+ } else {
+ $parts[] = "($q[1])";
+ }
+ break;
+ case "variable":
+ $parts[] = $this->compileValue($this->reduce($q));
+ break;
+ }
+ }
+
+ if (count($parts) > 0) {
+ $compiledQueries[] = implode(" and ", $parts);
+ }
+ }
+
+ $out = "@media";
+ if (!empty($parts)) {
+ $out .= " " .
+ implode($this->formatter->selectorSeparator, $compiledQueries);
+ }
+ return $out;
+ }
+
+ protected function multiplyMedia($env, $childQueries = null) {
+ if (is_null($env) ||
+ !empty($env->block->type) && $env->block->type != "media")
+ {
+ return $childQueries;
+ }
+
+ // plain old block, skip
+ if (empty($env->block->type)) {
+ return $this->multiplyMedia($env->parent, $childQueries);
+ }
+
+ $out = array();
+ $queries = $env->block->queries;
+ if (is_null($childQueries)) {
+ $out = $queries;
+ } else {
+ foreach ($queries as $parent) {
+ foreach ($childQueries as $child) {
+ $out[] = array_merge($parent, $child);
+ }
+ }
+ }
+
+ return $this->multiplyMedia($env->parent, $out);
+ }
+
+ protected function expandParentSelectors(&$tag, $replace) {
+ $parts = explode("$&$", $tag);
+ $count = 0;
+ foreach ($parts as &$part) {
+ $part = str_replace($this->parentSelector, $replace, $part, $c);
+ $count += $c;
+ }
+ $tag = implode($this->parentSelector, $parts);
+ return $count;
+ }
+
+ protected function findClosestSelectors() {
+ $env = $this->env;
+ $selectors = null;
+ while ($env !== null) {
+ if (isset($env->selectors)) {
+ $selectors = $env->selectors;
+ break;
+ }
+ $env = $env->parent;
+ }
+
+ return $selectors;
+ }
+
+
+ // multiply $selectors against the nearest selectors in env
+ protected function multiplySelectors($selectors) {
+ // find parent selectors
+
+ $parentSelectors = $this->findClosestSelectors();
+ if (is_null($parentSelectors)) {
+ // kill parent reference in top level selector
+ foreach ($selectors as &$s) {
+ $this->expandParentSelectors($s, "");
+ }
+
+ return $selectors;
+ }
+
+ $out = array();
+ foreach ($parentSelectors as $parent) {
+ foreach ($selectors as $child) {
+ $count = $this->expandParentSelectors($child, $parent);
+
+ // don't prepend the parent tag if & was used
+ if ($count > 0) {
+ $out[] = trim($child);
+ } else {
+ $out[] = trim($parent . ' ' . $child);
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ // reduces selector expressions
+ protected function compileSelectors($selectors) {
+ $out = array();
+
+ foreach ($selectors as $s) {
+ if (is_array($s)) {
+ list(, $value) = $s;
+ $out[] = trim($this->compileValue($this->reduce($value)));
+ } else {
+ $out[] = $s;
+ }
+ }
+
+ return $out;
+ }
+
+ protected function eq($left, $right) {
+ return $left == $right;
+ }
+
+ protected function patternMatch($block, $orderedArgs, $keywordArgs) {
+ // match the guards if it has them
+ // any one of the groups must have all its guards pass for a match
+ if (!empty($block->guards)) {
+ $groupPassed = false;
+ foreach ($block->guards as $guardGroup) {
+ foreach ($guardGroup as $guard) {
+ $this->pushEnv();
+ $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
+
+ $negate = false;
+ if ($guard[0] == "negate") {
+ $guard = $guard[1];
+ $negate = true;
+ }
+
+ $passed = $this->reduce($guard) == self::$TRUE;
+ if ($negate) $passed = !$passed;
+
+ $this->popEnv();
+
+ if ($passed) {
+ $groupPassed = true;
+ } else {
+ $groupPassed = false;
+ break;
+ }
+ }
+
+ if ($groupPassed) break;
+ }
+
+ if (!$groupPassed) {
+ return false;
+ }
+ }
+
+ if (empty($block->args)) {
+ return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
+ }
+
+ $remainingArgs = $block->args;
+ if ($keywordArgs) {
+ $remainingArgs = array();
+ foreach ($block->args as $arg) {
+ if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
+ continue;
+ }
+
+ $remainingArgs[] = $arg;
+ }
+ }
+
+ $i = -1; // no args
+ // try to match by arity or by argument literal
+ foreach ($remainingArgs as $i => $arg) {
+ switch ($arg[0]) {
+ case "lit":
+ if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
+ return false;
+ }
+ break;
+ case "arg":
+ // no arg and no default value
+ if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
+ return false;
+ }
+ break;
+ case "rest":
+ $i--; // rest can be empty
+ break 2;
+ }
+ }
+
+ if ($block->isVararg) {
+ return true; // not having enough is handled above
+ } else {
+ $numMatched = $i + 1;
+ // greater than becuase default values always match
+ return $numMatched >= count($orderedArgs);
+ }
+ }
+
+ protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
+ $matches = null;
+ foreach ($blocks as $block) {
+ // skip seen blocks that don't have arguments
+ if (isset($skip[$block->id]) && !isset($block->args)) {
+ continue;
+ }
+
+ if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
+ $matches[] = $block;
+ }
+ }
+
+ return $matches;
+ }
+
+ // attempt to find blocks matched by path and args
+ protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
+ if ($searchIn == null) return null;
+ if (isset($seen[$searchIn->id])) return null;
+ $seen[$searchIn->id] = true;
+
+ $name = $path[0];
+
+ if (isset($searchIn->children[$name])) {
+ $blocks = $searchIn->children[$name];
+ if (count($path) == 1) {
+ $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
+ if (!empty($matches)) {
+ // This will return all blocks that match in the closest
+ // scope that has any matching block, like lessjs
+ return $matches;
+ }
+ } else {
+ $matches = array();
+ foreach ($blocks as $subBlock) {
+ $subMatches = $this->findBlocks($subBlock,
+ array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
+
+ if (!is_null($subMatches)) {
+ foreach ($subMatches as $sm) {
+ $matches[] = $sm;
+ }
+ }
+ }
+
+ return count($matches) > 0 ? $matches : null;
+ }
+ }
+ if ($searchIn->parent === $searchIn) return null;
+ return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
+ }
+
+ // sets all argument names in $args to either the default value
+ // or the one passed in through $values
+ protected function zipSetArgs($args, $orderedValues, $keywordValues) {
+ $assignedValues = array();
+
+ $i = 0;
+ foreach ($args as $a) {
+ if ($a[0] == "arg") {
+ if (isset($keywordValues[$a[1]])) {
+ // has keyword arg
+ $value = $keywordValues[$a[1]];
+ } elseif (isset($orderedValues[$i])) {
+ // has ordered arg
+ $value = $orderedValues[$i];
+ $i++;
+ } elseif (isset($a[2])) {
+ // has default value
+ $value = $a[2];
+ } else {
+ $this->throwError("Failed to assign arg " . $a[1]);
+ $value = null; // :(
+ }
+
+ $value = $this->reduce($value);
+ $this->set($a[1], $value);
+ $assignedValues[] = $value;
+ } else {
+ // a lit
+ $i++;
+ }
+ }
+
+ // check for a rest
+ $last = end($args);
+ if ($last[0] == "rest") {
+ $rest = array_slice($orderedValues, count($args) - 1);
+ $this->set($last[1], $this->reduce(array("list", " ", $rest)));
+ }
+
+ // wow is this the only true use of PHP's + operator for arrays?
+ $this->env->arguments = $assignedValues + $orderedValues;
+ }
+
+ // compile a prop and update $lines or $blocks appropriately
+ protected function compileProp($prop, $block, $out) {
+ // set error position context
+ $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
+
+ switch ($prop[0]) {
+ case 'assign':
+ list(, $name, $value) = $prop;
+ if ($name[0] == $this->vPrefix) {
+ $this->set($name, $value);
+ } else {
+ $out->lines[] = $this->formatter->property($name,
+ $this->compileValue($this->reduce($value)));
+ }
+ break;
+ case 'block':
+ list(, $child) = $prop;
+ $this->compileBlock($child);
+ break;
+ case 'mixin':
+ list(, $path, $args, $suffix) = $prop;
+
+ $orderedArgs = array();
+ $keywordArgs = array();
+ foreach ((array)$args as $arg) {
+ $argval = null;
+ switch ($arg[0]) {
+ case "arg":
+ if (!isset($arg[2])) {
+ $orderedArgs[] = $this->reduce(array("variable", $arg[1]));
+ } else {
+ $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
+ }
+ break;
+
+ case "lit":
+ $orderedArgs[] = $this->reduce($arg[1]);
+ break;
+ default:
+ $this->throwError("Unknown arg type: " . $arg[0]);
+ }
+ }
+
+ $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
+
+ if ($mixins === null) {
+ $this->throwError("{$prop[1][0]} is undefined");
+ }
+
+ foreach ($mixins as $mixin) {
+ if ($mixin === $block && !$orderedArgs) {
+ continue;
+ }
+
+ $haveScope = false;
+ if (isset($mixin->parent->scope)) {
+ $haveScope = true;
+ $mixinParentEnv = $this->pushEnv();
+ $mixinParentEnv->storeParent = $mixin->parent->scope;
+ }
+
+ $haveArgs = false;
+ if (isset($mixin->args)) {
+ $haveArgs = true;
+ $this->pushEnv();
+ $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
+ }
+
+ $oldParent = $mixin->parent;
+ if ($mixin != $block) $mixin->parent = $block;
+
+ foreach ($this->sortProps($mixin->props) as $subProp) {
+ if ($suffix !== null &&
+ $subProp[0] == "assign" &&
+ is_string($subProp[1]) &&
+ $subProp[1]{0} != $this->vPrefix)
+ {
+ $subProp[2] = array(
+ 'list', ' ',
+ array($subProp[2], array('keyword', $suffix))
+ );
+ }
+
+ $this->compileProp($subProp, $mixin, $out);
+ }
+
+ $mixin->parent = $oldParent;
+
+ if ($haveArgs) $this->popEnv();
+ if ($haveScope) $this->popEnv();
+ }
+
+ break;
+ case 'raw':
+ $out->lines[] = $prop[1];
+ break;
+ case "directive":
+ list(, $name, $value) = $prop;
+ $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
+ break;
+ case "comment":
+ $out->lines[] = $prop[1];
+ break;
+ case "import";
+ list(, $importPath, $importId) = $prop;
+ $importPath = $this->reduce($importPath);
+
+ if (!isset($this->env->imports)) {
+ $this->env->imports = array();
+ }
+
+ $result = $this->tryImport($importPath, $block, $out);
+
+ $this->env->imports[$importId] = $result === false ?
+ array(false, "@import " . $this->compileValue($importPath).";") :
+ $result;
+
+ break;
+ case "import_mixin":
+ list(,$importId) = $prop;
+ $import = $this->env->imports[$importId];
+ if ($import[0] === false) {
+ if (isset($import[1])) {
+ $out->lines[] = $import[1];
+ }
+ } else {
+ list(, $bottom, $parser, $importDir) = $import;
+ $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
+ }
+
+ break;
+ default:
+ $this->throwError("unknown op: {$prop[0]}\n");
+ }
+ }
+
+
+ /**
+ * Compiles a primitive value into a CSS property value.
+ *
+ * Values in lessphp are typed by being wrapped in arrays, their format is
+ * typically:
+ *
+ * array(type, contents [, additional_contents]*)
+ *
+ * The input is expected to be reduced. This function will not work on
+ * things like expressions and variables.
+ */
+ public function compileValue($value) {
+ switch ($value[0]) {
+ case 'list':
+ // [1] - delimiter
+ // [2] - array of values
+ return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
+ case 'raw_color':
+ if (!empty($this->formatter->compressColors)) {
+ return $this->compileValue($this->coerceColor($value));
+ }
+ return $value[1];
+ case 'keyword':
+ // [1] - the keyword
+ return $value[1];
+ case 'number':
+ list(, $num, $unit) = $value;
+ // [1] - the number
+ // [2] - the unit
+ if ($this->numberPrecision !== null) {
+ $num = round($num, $this->numberPrecision);
+ }
+ return $num . $unit;
+ case 'string':
+ // [1] - contents of string (includes quotes)
+ list(, $delim, $content) = $value;
+ foreach ($content as &$part) {
+ if (is_array($part)) {
+ $part = $this->compileValue($part);
+ }
+ }
+ return $delim . implode($content) . $delim;
+ case 'color':
+ // [1] - red component (either number or a %)
+ // [2] - green component
+ // [3] - blue component
+ // [4] - optional alpha component
+ list(, $r, $g, $b) = $value;
+ $r = round($r);
+ $g = round($g);
+ $b = round($b);
+
+ if (count($value) == 5 && $value[4] != 1) { // rgba
+ return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
+ }
+
+ $h = sprintf("#%02x%02x%02x", $r, $g, $b);
+
+ if (!empty($this->formatter->compressColors)) {
+ // Converting hex color to short notation (e.g. #003399 to #039)
+ if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
+ $h = '#' . $h[1] . $h[3] . $h[5];
+ }
+ }
+
+ return $h;
+
+ case 'function':
+ list(, $name, $args) = $value;
+ return $name.'('.$this->compileValue($args).')';
+ default: // assumed to be unit
+ $this->throwError("unknown value type: $value[0]");
+ }
+ }
+
+ protected function lib_pow($args) {
+ list($base, $exp) = $this->assertArgs($args, 2, "pow");
+ return pow($this->assertNumber($base), $this->assertNumber($exp));
+ }
+
+ protected function lib_pi() {
+ return pi();
+ }
+
+ protected function lib_mod($args) {
+ list($a, $b) = $this->assertArgs($args, 2, "mod");
+ return $this->assertNumber($a) % $this->assertNumber($b);
+ }
+
+ protected function lib_tan($num) {
+ return tan($this->assertNumber($num));
+ }
+
+ protected function lib_sin($num) {
+ return sin($this->assertNumber($num));
+ }
+
+ protected function lib_cos($num) {
+ return cos($this->assertNumber($num));
+ }
+
+ protected function lib_atan($num) {
+ $num = atan($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_asin($num) {
+ $num = asin($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_acos($num) {
+ $num = acos($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_sqrt($num) {
+ return sqrt($this->assertNumber($num));
+ }
+
+ protected function lib_extract($value) {
+ list($list, $idx) = $this->assertArgs($value, 2, "extract");
+ $idx = $this->assertNumber($idx);
+ // 1 indexed
+ if ($list[0] == "list" && isset($list[2][$idx - 1])) {
+ return $list[2][$idx - 1];
+ }
+ }
+
+ protected function lib_isnumber($value) {
+ return $this->toBool($value[0] == "number");
+ }
+
+ protected function lib_isstring($value) {
+ return $this->toBool($value[0] == "string");
+ }
+
+ protected function lib_iscolor($value) {
+ return $this->toBool($this->coerceColor($value));
+ }
+
+ protected function lib_iskeyword($value) {
+ return $this->toBool($value[0] == "keyword");
+ }
+
+ protected function lib_ispixel($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "px");
+ }
+
+ protected function lib_ispercentage($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "%");
+ }
+
+ protected function lib_isem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "em");
+ }
+
+ protected function lib_isrem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "rem");
+ }
+
+ protected function lib_rgbahex($color) {
+ $color = $this->coerceColor($color);
+ if (is_null($color))
+ $this->throwError("color expected for rgbahex");
+
+ return sprintf("#%02x%02x%02x%02x",
+ isset($color[4]) ? $color[4]*255 : 255,
+ $color[1],$color[2], $color[3]);
+ }
+
+ protected function lib_argb($color){
+ return $this->lib_rgbahex($color);
+ }
+
+ /**
+ * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
+ *
+ * @param array $value either an argument list (two strings) or a single string
+ * @return string formatted url(), either as a link or base64-encoded
+ */
+ protected function lib_data_uri($value) {
+ $mime = ($value[0] === 'list') ? $value[2][0][2] : null;
+ $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
+
+ $fullpath = $this->findImport($url);
+
+ if($fullpath && ($fsize = filesize($fullpath)) !== false) {
+ // IE8 can't handle data uris larger than 32KB
+ if($fsize/1024 < 32) {
+ if(is_null($mime)) {
+ if(class_exists('finfo')) { // php 5.3+
+ // phpcs:ignore PHPCompatibility.PHP.NewClasses.finfoFound
+ $finfo = new finfo(FILEINFO_MIME);
+ $mime = explode('; ', $finfo->file($fullpath));
+ $mime = $mime[0];
+ } elseif(function_exists('mime_content_type')) { // PHP 5.2
+ $mime = mime_content_type($fullpath);
+ }
+ }
+
+ if(!is_null($mime)) // fallback if the mime type is still unknown
+ $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
+ }
+ }
+
+ return 'url("'.$url.'")';
+ }
+
+ // utility func to unquote a string
+ protected function lib_e($arg) {
+ switch ($arg[0]) {
+ case "list":
+ $items = $arg[2];
+ if (isset($items[0])) {
+ return $this->lib_e($items[0]);
+ }
+ $this->throwError("unrecognised input");
+ case "string":
+ $arg[1] = "";
+ return $arg;
+ case "keyword":
+ return $arg;
+ default:
+ return array("keyword", $this->compileValue($arg));
+ }
+ }
+
+ protected function lib__sprintf($args) {
+ if ($args[0] != "list") return $args;
+ $values = $args[2];
+ $string = array_shift($values);
+ $template = $this->compileValue($this->lib_e($string));
+
+ $i = 0;
+ if (preg_match_all('/%[dsa]/', $template, $m)) {
+ foreach ($m[0] as $match) {
+ $val = isset($values[$i]) ?
+ $this->reduce($values[$i]) : array('keyword', '');
+
+ // lessjs compat, renders fully expanded color, not raw color
+ if ($color = $this->coerceColor($val)) {
+ $val = $color;
+ }
+
+ $i++;
+ $rep = $this->compileValue($this->lib_e($val));
+ $template = preg_replace('/'.self::preg_quote($match).'/',
+ $rep, $template, 1);
+ }
+ }
+
+ $d = $string[0] == "string" ? $string[1] : '"';
+ return array("string", $d, array($template));
+ }
+
+ protected function lib_floor($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", floor($value), $arg[2]);
+ }
+
+ protected function lib_ceil($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", ceil($value), $arg[2]);
+ }
+
+ protected function lib_round($arg) {
+ if($arg[0] != "list") {
+ $value = $this->assertNumber($arg);
+ return array("number", round($value), $arg[2]);
+ } else {
+ $value = $this->assertNumber($arg[2][0]);
+ $precision = $this->assertNumber($arg[2][1]);
+ return array("number", round($value, $precision), $arg[2][0][2]);
+ }
+ }
+
+ protected function lib_unit($arg) {
+ if ($arg[0] == "list") {
+ list($number, $newUnit) = $arg[2];
+ return array("number", $this->assertNumber($number),
+ $this->compileValue($this->lib_e($newUnit)));
+ } else {
+ return array("number", $this->assertNumber($arg), "");
+ }
+ }
+
+ /**
+ * Helper function to get arguments for color manipulation functions.
+ * takes a list that contains a color like thing and a percentage
+ */
+ public function colorArgs($args) {
+ if ($args[0] != 'list' || count($args[2]) < 2) {
+ return array(array('color', 0, 0, 0), 0);
+ }
+ list($color, $delta) = $args[2];
+ $color = $this->assertColor($color);
+ $delta = floatval($delta[1]);
+
+ return array($color, $delta);
+ }
+
+ protected function lib_darken($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_lighten($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_saturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_desaturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_spin($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+
+ $hsl[1] = $hsl[1] + $delta % 360;
+ if ($hsl[1] < 0) $hsl[1] += 360;
+
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_fadeout($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
+ return $color;
+ }
+
+ protected function lib_fadein($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
+ return $color;
+ }
+
+ protected function lib_hue($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[1]);
+ }
+
+ protected function lib_saturation($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[2]);
+ }
+
+ protected function lib_lightness($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[3]);
+ }
+
+ // get the alpha of a color
+ // defaults to 1 for non-colors or colors without an alpha
+ protected function lib_alpha($value) {
+ if (!is_null($color = $this->coerceColor($value))) {
+ return isset($color[4]) ? $color[4] : 1;
+ }
+ }
+
+ // set the alpha of the color
+ protected function lib_fade($args) {
+ list($color, $alpha) = $this->colorArgs($args);
+ $color[4] = $this->clamp($alpha / 100.0);
+ return $color;
+ }
+
+ protected function lib_percentage($arg) {
+ $num = $this->assertNumber($arg);
+ return array("number", $num*100, "%");
+ }
+
+ // mixes two colors by weight
+ // mix(@color1, @color2, [@weight: 50%]);
+ // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
+ protected function lib_mix($args) {
+ if ($args[0] != "list" || count($args[2]) < 2)
+ $this->throwError("mix expects (color1, color2, weight)");
+
+ list($first, $second) = $args[2];
+ $first = $this->assertColor($first);
+ $second = $this->assertColor($second);
+
+ $first_a = $this->lib_alpha($first);
+ $second_a = $this->lib_alpha($second);
+
+ if (isset($args[2][2])) {
+ $weight = $args[2][2][1] / 100.0;
+ } else {
+ $weight = 0.5;
+ }
+
+ $w = $weight * 2 - 1;
+ $a = $first_a - $second_a;
+
+ $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
+ $w2 = 1.0 - $w1;
+
+ $new = array('color',
+ $w1 * $first[1] + $w2 * $second[1],
+ $w1 * $first[2] + $w2 * $second[2],
+ $w1 * $first[3] + $w2 * $second[3],
+ );
+
+ if ($first_a != 1.0 || $second_a != 1.0) {
+ $new[] = $first_a * $weight + $second_a * ($weight - 1);
+ }
+
+ return $this->fixColor($new);
+ }
+
+ protected function lib_contrast($args) {
+ $darkColor = array('color', 0, 0, 0);
+ $lightColor = array('color', 255, 255, 255);
+ $threshold = 0.43;
+
+ if ( $args[0] == 'list' ) {
+ $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor;
+ $darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor;
+ $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor;
+ $threshold = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
+ }
+ else {
+ $inputColor = $this->assertColor($args);
+ }
+
+ $inputColor = $this->coerceColor($inputColor);
+ $darkColor = $this->coerceColor($darkColor);
+ $lightColor = $this->coerceColor($lightColor);
+
+ //Figure out which is actually light and dark!
+ if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
+ $t = $lightColor;
+ $lightColor = $darkColor;
+ $darkColor = $t;
+ }
+
+ $inputColor_alpha = $this->lib_alpha($inputColor);
+ if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
+ return $lightColor;
+ }
+ return $darkColor;
+ }
+
+ protected function lib_luma($color) {
+ $color = $this->coerceColor($color);
+ return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
+ }
+
+
+ public function assertColor($value, $error = "expected color value") {
+ $color = $this->coerceColor($value);
+ if (is_null($color)) $this->throwError($error);
+ return $color;
+ }
+
+ public function assertNumber($value, $error = "expecting number") {
+ if ($value[0] == "number") return $value[1];
+ $this->throwError($error);
+ }
+
+ public function assertArgs($value, $expectedArgs, $name="") {
+ if ($expectedArgs == 1) {
+ return $value;
+ } else {
+ if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
+ $values = $value[2];
+ $numValues = count($values);
+ if ($expectedArgs != $numValues) {
+ if ($name) {
+ $name = $name . ": ";
+ }
+
+ $this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
+ }
+
+ return $values;
+ }
+ }
+
+ protected function toHSL($color) {
+ if ($color[0] == 'hsl') return $color;
+
+ $r = $color[1] / 255;
+ $g = $color[2] / 255;
+ $b = $color[3] / 255;
+
+ $min = min($r, $g, $b);
+ $max = max($r, $g, $b);
+
+ $L = ($min + $max) / 2;
+ if ($min == $max) {
+ $S = $H = 0;
+ } else {
+ if ($L < 0.5)
+ $S = ($max - $min)/($max + $min);
+ else
+ $S = ($max - $min)/(2.0 - $max - $min);
+
+ if ($r == $max) $H = ($g - $b)/($max - $min);
+ elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
+ elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
+
+ }
+
+ $out = array('hsl',
+ ($H < 0 ? $H + 6 : $H)*60,
+ $S*100,
+ $L*100,
+ );
+
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function toRGB_helper($comp, $temp1, $temp2) {
+ if ($comp < 0) $comp += 1.0;
+ elseif ($comp > 1) $comp -= 1.0;
+
+ if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
+ if (2 * $comp < 1) return $temp2;
+ if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
+
+ return $temp1;
+ }
+
+ /**
+ * Converts a hsl array into a color value in rgb.
+ * Expects H to be in range of 0 to 360, S and L in 0 to 100
+ */
+ protected function toRGB($color) {
+ if ($color[0] == 'color') return $color;
+
+ $H = $color[1] / 360;
+ $S = $color[2] / 100;
+ $L = $color[3] / 100;
+
+ if ($S == 0) {
+ $r = $g = $b = $L;
+ } else {
+ $temp2 = $L < 0.5 ?
+ $L*(1.0 + $S) :
+ $L + $S - $L * $S;
+
+ $temp1 = 2.0 * $L - $temp2;
+
+ $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
+ $g = $this->toRGB_helper($H, $temp1, $temp2);
+ $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
+ }
+
+ // $out = array('color', round($r*255), round($g*255), round($b*255));
+ $out = array('color', $r*255, $g*255, $b*255);
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function clamp($v, $max = 1, $min = 0) {
+ return min($max, max($min, $v));
+ }
+
+ /**
+ * Convert the rgb, rgba, hsl color literals of function type
+ * as returned by the parser into values of color type.
+ */
+ protected function funcToColor($func) {
+ $fname = $func[1];
+ if ($func[2][0] != 'list') return false; // need a list of arguments
+ $rawComponents = $func[2][2];
+
+ if ($fname == 'hsl' || $fname == 'hsla') {
+ $hsl = array('hsl');
+ $i = 0;
+ foreach ($rawComponents as $c) {
+ $val = $this->reduce($c);
+ $val = isset($val[1]) ? floatval($val[1]) : 0;
+
+ if ($i == 0) $clamp = 360;
+ elseif ($i < 3) $clamp = 100;
+ else $clamp = 1;
+
+ $hsl[] = $this->clamp($val, $clamp);
+ $i++;
+ }
+
+ while (count($hsl) < 4) $hsl[] = 0;
+ return $this->toRGB($hsl);
+
+ } elseif ($fname == 'rgb' || $fname == 'rgba') {
+ $components = array();
+ $i = 1;
+ foreach ($rawComponents as $c) {
+ $c = $this->reduce($c);
+ if ($i < 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 255 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } elseif ($i == 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 1.0 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } else break;
+
+ $i++;
+ }
+ while (count($components) < 3) $components[] = 0;
+ array_unshift($components, 'color');
+ return $this->fixColor($components);
+ }
+
+ return false;
+ }
+
+ protected function reduce($value, $forExpression = false) {
+ switch ($value[0]) {
+ case "interpolate":
+ $reduced = $this->reduce($value[1]);
+ $var = $this->compileValue($reduced);
+ $res = $this->reduce(array("variable", $this->vPrefix . $var));
+
+ if ($res[0] == "raw_color") {
+ $res = $this->coerceColor($res);
+ }
+
+ if (empty($value[2])) $res = $this->lib_e($res);
+
+ return $res;
+ case "variable":
+ $key = $value[1];
+ if (is_array($key)) {
+ $key = $this->reduce($key);
+ $key = $this->vPrefix . $this->compileValue($this->lib_e($key));
+ }
+
+ $seen =& $this->env->seenNames;
+
+ if (!empty($seen[$key])) {
+ $this->throwError("infinite loop detected: $key");
+ }
+
+ $seen[$key] = true;
+ $out = $this->reduce($this->get($key));
+ $seen[$key] = false;
+ return $out;
+ case "list":
+ foreach ($value[2] as &$item) {
+ $item = $this->reduce($item, $forExpression);
+ }
+ return $value;
+ case "expression":
+ return $this->evaluate($value);
+ case "string":
+ foreach ($value[2] as &$part) {
+ if (is_array($part)) {
+ $strip = $part[0] == "variable";
+ $part = $this->reduce($part);
+ if ($strip) $part = $this->lib_e($part);
+ }
+ }
+ return $value;
+ case "escape":
+ list(,$inner) = $value;
+ return $this->lib_e($this->reduce($inner));
+ case "function":
+ $color = $this->funcToColor($value);
+ if ($color) return $color;
+
+ list(, $name, $args) = $value;
+ if ($name == "%") $name = "_sprintf";
+
+ $f = isset($this->libFunctions[$name]) ?
+ $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
+
+ if (is_callable($f)) {
+ if ($args[0] == 'list')
+ $args = self::compressList($args[2], $args[1]);
+
+ $ret = call_user_func($f, $this->reduce($args, true), $this);
+
+ if (is_null($ret)) {
+ return array("string", "", array(
+ $name, "(", $args, ")"
+ ));
+ }
+
+ // convert to a typed value if the result is a php primitive
+ if (is_numeric($ret)) $ret = array('number', $ret, "");
+ elseif (!is_array($ret)) $ret = array('keyword', $ret);
+
+ return $ret;
+ }
+
+ // plain function, reduce args
+ $value[2] = $this->reduce($value[2]);
+ return $value;
+ case "unary":
+ list(, $op, $exp) = $value;
+ $exp = $this->reduce($exp);
+
+ if ($exp[0] == "number") {
+ switch ($op) {
+ case "+":
+ return $exp;
+ case "-":
+ $exp[1] *= -1;
+ return $exp;
+ }
+ }
+ return array("string", "", array($op, $exp));
+ }
+
+ if ($forExpression) {
+ switch ($value[0]) {
+ case "keyword":
+ if ($color = $this->coerceColor($value)) {
+ return $color;
+ }
+ break;
+ case "raw_color":
+ return $this->coerceColor($value);
+ }
+ }
+
+ return $value;
+ }
+
+
+ // coerce a value for use in color operation
+ protected function coerceColor($value) {
+ switch($value[0]) {
+ case 'color': return $value;
+ case 'raw_color':
+ $c = array("color", 0, 0, 0);
+ $colorStr = substr($value[1], 1);
+ $num = hexdec($colorStr);
+ $width = strlen($colorStr) == 3 ? 16 : 256;
+
+ for ($i = 3; $i > 0; $i--) { // 3 2 1
+ $t = $num % $width;
+ $num /= $width;
+
+ $c[$i] = $t * (256/$width) + $t * floor(16/$width);
+ }
+
+ return $c;
+ case 'keyword':
+ $name = $value[1];
+ if (isset(self::$cssColors[$name])) {
+ $rgba = explode(',', self::$cssColors[$name]);
+
+ if(isset($rgba[3]))
+ return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
+
+ return array('color', $rgba[0], $rgba[1], $rgba[2]);
+ }
+ return null;
+ }
+ }
+
+ // make something string like into a string
+ protected function coerceString($value) {
+ switch ($value[0]) {
+ case "string":
+ return $value;
+ case "keyword":
+ return array("string", "", array($value[1]));
+ }
+ return null;
+ }
+
+ // turn list of length 1 into value type
+ protected function flattenList($value) {
+ if ($value[0] == "list" && count($value[2]) == 1) {
+ return $this->flattenList($value[2][0]);
+ }
+ return $value;
+ }
+
+ public function toBool($a) {
+ if ($a) return self::$TRUE;
+ else return self::$FALSE;
+ }
+
+ // evaluate an expression
+ protected function evaluate($exp) {
+ list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
+
+ $left = $this->reduce($left, true);
+ $right = $this->reduce($right, true);
+
+ if ($leftColor = $this->coerceColor($left)) {
+ $left = $leftColor;
+ }
+
+ if ($rightColor = $this->coerceColor($right)) {
+ $right = $rightColor;
+ }
+
+ $ltype = $left[0];
+ $rtype = $right[0];
+
+ // operators that work on all types
+ if ($op == "and") {
+ return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
+ }
+
+ if ($op == "=") {
+ return $this->toBool($this->eq($left, $right) );
+ }
+
+ if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
+ return $str;
+ }
+
+ // type based operators
+ $fname = "op_${ltype}_${rtype}";
+ if (is_callable(array($this, $fname))) {
+ $out = $this->$fname($op, $left, $right);
+ if (!is_null($out)) return $out;
+ }
+
+ // make the expression look it did before being parsed
+ $paddedOp = $op;
+ if ($whiteBefore) $paddedOp = " " . $paddedOp;
+ if ($whiteAfter) $paddedOp .= " ";
+
+ return array("string", "", array($left, $paddedOp, $right));
+ }
+
+ protected function stringConcatenate($left, $right) {
+ if ($strLeft = $this->coerceString($left)) {
+ if ($right[0] == "string") {
+ $right[1] = "";
+ }
+ $strLeft[2][] = $right;
+ return $strLeft;
+ }
+
+ if ($strRight = $this->coerceString($right)) {
+ array_unshift($strRight[2], $left);
+ return $strRight;
+ }
+ }
+
+
+ // make sure a color's components don't go out of bounds
+ protected function fixColor($c) {
+ foreach (range(1, 3) as $i) {
+ if ($c[$i] < 0) $c[$i] = 0;
+ if ($c[$i] > 255) $c[$i] = 255;
+ }
+
+ return $c;
+ }
+
+ protected function op_number_color($op, $lft, $rgt) {
+ if ($op == '+' || $op == '*') {
+ return $this->op_color_number($op, $rgt, $lft);
+ }
+ }
+
+ protected function op_color_number($op, $lft, $rgt) {
+ if ($rgt[0] == '%') $rgt[1] /= 100;
+
+ return $this->op_color_color($op, $lft,
+ array_fill(1, count($lft) - 1, $rgt[1]));
+ }
+
+ protected function op_color_color($op, $left, $right) {
+ $out = array('color');
+ $max = count($left) > count($right) ? count($left) : count($right);
+ foreach (range(1, $max - 1) as $i) {
+ $lval = isset($left[$i]) ? $left[$i] : 0;
+ $rval = isset($right[$i]) ? $right[$i] : 0;
+ switch ($op) {
+ case '+':
+ $out[] = $lval + $rval;
+ break;
+ case '-':
+ $out[] = $lval - $rval;
+ break;
+ case '*':
+ $out[] = $lval * $rval;
+ break;
+ case '%':
+ $out[] = $lval % $rval;
+ break;
+ case '/':
+ if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
+ $out[] = $lval / $rval;
+ break;
+ default:
+ $this->throwError('evaluate error: color op number failed on op '.$op);
+ }
+ }
+ return $this->fixColor($out);
+ }
+
+ function lib_red($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for red()');
+ }
+
+ return $color[1];
+ }
+
+ function lib_green($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for green()');
+ }
+
+ return $color[2];
+ }
+
+ function lib_blue($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for blue()');
+ }
+
+ return $color[3];
+ }
+
+
+ // operator on two numbers
+ protected function op_number_number($op, $left, $right) {
+ $unit = empty($left[2]) ? $right[2] : $left[2];
+
+ $value = 0;
+ switch ($op) {
+ case '+':
+ $value = $left[1] + $right[1];
+ break;
+ case '*':
+ $value = $left[1] * $right[1];
+ break;
+ case '-':
+ $value = $left[1] - $right[1];
+ break;
+ case '%':
+ $value = $left[1] % $right[1];
+ break;
+ case '/':
+ if ($right[1] == 0) $this->throwError('parse error: divide by zero');
+ $value = $left[1] / $right[1];
+ break;
+ case '<':
+ return $this->toBool($left[1] < $right[1]);
+ case '>':
+ return $this->toBool($left[1] > $right[1]);
+ case '>=':
+ return $this->toBool($left[1] >= $right[1]);
+ case '=<':
+ return $this->toBool($left[1] <= $right[1]);
+ default:
+ $this->throwError('parse error: unknown number operator: '.$op);
+ }
+
+ return array("number", $value, $unit);
+ }
+
+
+ /* environment functions */
+
+ protected function makeOutputBlock($type, $selectors = null) {
+ $b = new stdclass;
+ $b->lines = array();
+ $b->children = array();
+ $b->selectors = $selectors;
+ $b->type = $type;
+ $b->parent = $this->scope;
+ return $b;
+ }
+
+ // the state of execution
+ protected function pushEnv($block = null) {
+ $e = new stdclass;
+ $e->parent = $this->env;
+ $e->store = array();
+ $e->block = $block;
+
+ $this->env = $e;
+ return $e;
+ }
+
+ // pop something off the stack
+ protected function popEnv() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // set something in the current env
+ protected function set($name, $value) {
+ $this->env->store[$name] = $value;
+ }
+
+
+ // get the highest occurrence entry for a name
+ protected function get($name) {
+ $current = $this->env;
+
+ $isArguments = $name == $this->vPrefix . 'arguments';
+ while ($current) {
+ if ($isArguments && isset($current->arguments)) {
+ return array('list', ' ', $current->arguments);
+ }
+
+ if (isset($current->store[$name]))
+ return $current->store[$name];
+ else {
+ $current = isset($current->storeParent) ?
+ $current->storeParent : $current->parent;
+ }
+ }
+
+ $this->throwError("variable $name is undefined");
+ }
+
+ // inject array of unparsed strings into environment as variables
+ protected function injectVariables($args) {
+ $this->pushEnv();
+ $parser = new lessc_parser($this, __METHOD__);
+ foreach ($args as $name => $strValue) {
+ if ($name{0} != '@') $name = '@'.$name;
+ $parser->count = 0;
+ $parser->buffer = (string)$strValue;
+ if (!$parser->propertyValue($value)) {
+ throw new Exception("failed to parse passed in variable $name: $strValue");
+ }
+
+ $this->set($name, $value);
+ }
+ }
+
+ /**
+ * Initialize any static state, can initialize parser for a file
+ * $opts isn't used yet
+ */
+ public function __construct($fname = null) {
+ if ($fname !== null) {
+ // used for deprecated parse method
+ $this->_parseFile = $fname;
+ }
+ }
+
+ public function compile($string, $name = null) {
+ $locale = setlocale(LC_NUMERIC, 0);
+ setlocale(LC_NUMERIC, "C");
+
+ $this->parser = $this->makeParser($name);
+ $root = $this->parser->parse($string);
+
+ $this->env = null;
+ $this->scope = null;
+
+ $this->formatter = $this->newFormatter();
+
+ if (!empty($this->registeredVars)) {
+ $this->injectVariables($this->registeredVars);
+ }
+
+ $this->sourceParser = $this->parser; // used for error messages
+ $this->compileBlock($root);
+
+ ob_start();
+ $this->formatter->block($this->scope);
+ $out = ob_get_clean();
+ setlocale(LC_NUMERIC, $locale);
+ return $out;
+ }
+
+ public function compileFile($fname, $outFname = null) {
+ if (!is_readable($fname)) {
+ throw new Exception('load error: failed to find '.$fname);
+ }
+
+ $pi = pathinfo($fname);
+
+ $oldImport = $this->importDir;
+
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $pi['dirname'].'/';
+
+ $this->addParsedFile($fname);
+
+ $out = $this->compile(file_get_contents($fname), $fname);
+
+ $this->importDir = $oldImport;
+
+ if ($outFname !== null) {
+ return file_put_contents($outFname, $out);
+ }
+
+ return $out;
+ }
+
+ // compile only if changed input has changed or output doesn't exist
+ public function checkedCompile($in, $out) {
+ if (!is_file($out) || filemtime($in) > filemtime($out)) {
+ $this->compileFile($in, $out);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Execute lessphp on a .less file or a lessphp cache structure
+ *
+ * The lessphp cache structure contains information about a specific
+ * less file having been parsed. It can be used as a hint for future
+ * calls to determine whether or not a rebuild is required.
+ *
+ * The cache structure contains two important keys that may be used
+ * externally:
+ *
+ * compiled: The final compiled CSS
+ * updated: The time (in seconds) the CSS was last compiled
+ *
+ * The cache structure is a plain-ol' PHP associative array and can
+ * be serialized and unserialized without a hitch.
+ *
+ * @param mixed $in Input
+ * @param bool $force Force rebuild?
+ * @return array lessphp cache structure
+ */
+ public function cachedCompile($in, $force = false) {
+ // assume no root
+ $root = null;
+
+ if (is_string($in)) {
+ $root = $in;
+ } elseif (is_array($in) and isset($in['root'])) {
+ if ($force or ! isset($in['files'])) {
+ // If we are forcing a recompile or if for some reason the
+ // structure does not contain any file information we should
+ // specify the root to trigger a rebuild.
+ $root = $in['root'];
+ } elseif (isset($in['files']) and is_array($in['files'])) {
+ foreach ($in['files'] as $fname => $ftime ) {
+ if (!file_exists($fname) or filemtime($fname) > $ftime) {
+ // One of the files we knew about previously has changed
+ // so we should look at our incoming root again.
+ $root = $in['root'];
+ break;
+ }
+ }
+ }
+ } else {
+ // TODO: Throw an exception? We got neither a string nor something
+ // that looks like a compatible lessphp cache structure.
+ return null;
+ }
+
+ if ($root !== null) {
+ // If we have a root value which means we should rebuild.
+ $out = array();
+ $out['root'] = $root;
+ $out['compiled'] = $this->compileFile($root);
+ $out['files'] = $this->allParsedFiles();
+ $out['updated'] = time();
+ return $out;
+ } else {
+ // No changes, pass back the structure
+ // we were given initially.
+ return $in;
+ }
+
+ }
+
+ // parse and compile buffer
+ // This is deprecated
+ public function parse($str = null, $initialVariables = null) {
+ if (is_array($str)) {
+ $initialVariables = $str;
+ $str = null;
+ }
+
+ $oldVars = $this->registeredVars;
+ if ($initialVariables !== null) {
+ $this->setVariables($initialVariables);
+ }
+
+ if ($str == null) {
+ if (empty($this->_parseFile)) {
+ throw new exception("nothing to parse");
+ }
+
+ $out = $this->compileFile($this->_parseFile);
+ } else {
+ $out = $this->compile($str);
+ }
+
+ $this->registeredVars = $oldVars;
+ return $out;
+ }
+
+ protected function makeParser($name) {
+ $parser = new lessc_parser($this, $name);
+ $parser->writeComments = $this->preserveComments;
+
+ return $parser;
+ }
+
+ public function setFormatter($name) {
+ $this->formatterName = $name;
+ }
+
+ protected function newFormatter() {
+ $className = "lessc_formatter_lessjs";
+ if (!empty($this->formatterName)) {
+ if (!is_string($this->formatterName))
+ return $this->formatterName;
+ $className = "lessc_formatter_$this->formatterName";
+ }
+
+ return new $className;
+ }
+
+ public function setPreserveComments($preserve) {
+ $this->preserveComments = $preserve;
+ }
+
+ public function registerFunction($name, $func) {
+ $this->libFunctions[$name] = $func;
+ }
+
+ public function unregisterFunction($name) {
+ unset($this->libFunctions[$name]);
+ }
+
+ public function setVariables($variables) {
+ $this->registeredVars = array_merge($this->registeredVars, $variables);
+ }
+
+ public function unsetVariable($name) {
+ unset($this->registeredVars[$name]);
+ }
+
+ public function setImportDir($dirs) {
+ $this->importDir = (array)$dirs;
+ }
+
+ public function addImportDir($dir) {
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $dir;
+ }
+
+ public function allParsedFiles() {
+ return $this->allParsedFiles;
+ }
+
+ public function addParsedFile($file) {
+ $this->allParsedFiles[realpath($file)] = filemtime($file);
+ }
+
+ /**
+ * Uses the current value of $this->count to show line and line number
+ */
+ public function throwError($msg = null) {
+ if ($this->sourceLoc >= 0) {
+ $this->sourceParser->throwError($msg, $this->sourceLoc);
+ }
+ throw new exception($msg);
+ }
+
+ // compile file $in to file $out if $in is newer than $out
+ // returns true when it compiles, false otherwise
+ public static function ccompile($in, $out, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->checkedCompile($in, $out);
+ }
+
+ public static function cexecute($in, $force = false, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->cachedCompile($in, $force);
+ }
+
+ static protected $cssColors = array(
+ 'aliceblue' => '240,248,255',
+ 'antiquewhite' => '250,235,215',
+ 'aqua' => '0,255,255',
+ 'aquamarine' => '127,255,212',
+ 'azure' => '240,255,255',
+ 'beige' => '245,245,220',
+ 'bisque' => '255,228,196',
+ 'black' => '0,0,0',
+ 'blanchedalmond' => '255,235,205',
+ 'blue' => '0,0,255',
+ 'blueviolet' => '138,43,226',
+ 'brown' => '165,42,42',
+ 'burlywood' => '222,184,135',
+ 'cadetblue' => '95,158,160',
+ 'chartreuse' => '127,255,0',
+ 'chocolate' => '210,105,30',
+ 'coral' => '255,127,80',
+ 'cornflowerblue' => '100,149,237',
+ 'cornsilk' => '255,248,220',
+ 'crimson' => '220,20,60',
+ 'cyan' => '0,255,255',
+ 'darkblue' => '0,0,139',
+ 'darkcyan' => '0,139,139',
+ 'darkgoldenrod' => '184,134,11',
+ 'darkgray' => '169,169,169',
+ 'darkgreen' => '0,100,0',
+ 'darkgrey' => '169,169,169',
+ 'darkkhaki' => '189,183,107',
+ 'darkmagenta' => '139,0,139',
+ 'darkolivegreen' => '85,107,47',
+ 'darkorange' => '255,140,0',
+ 'darkorchid' => '153,50,204',
+ 'darkred' => '139,0,0',
+ 'darksalmon' => '233,150,122',
+ 'darkseagreen' => '143,188,143',
+ 'darkslateblue' => '72,61,139',
+ 'darkslategray' => '47,79,79',
+ 'darkslategrey' => '47,79,79',
+ 'darkturquoise' => '0,206,209',
+ 'darkviolet' => '148,0,211',
+ 'deeppink' => '255,20,147',
+ 'deepskyblue' => '0,191,255',
+ 'dimgray' => '105,105,105',
+ 'dimgrey' => '105,105,105',
+ 'dodgerblue' => '30,144,255',
+ 'firebrick' => '178,34,34',
+ 'floralwhite' => '255,250,240',
+ 'forestgreen' => '34,139,34',
+ 'fuchsia' => '255,0,255',
+ 'gainsboro' => '220,220,220',
+ 'ghostwhite' => '248,248,255',
+ 'gold' => '255,215,0',
+ 'goldenrod' => '218,165,32',
+ 'gray' => '128,128,128',
+ 'green' => '0,128,0',
+ 'greenyellow' => '173,255,47',
+ 'grey' => '128,128,128',
+ 'honeydew' => '240,255,240',
+ 'hotpink' => '255,105,180',
+ 'indianred' => '205,92,92',
+ 'indigo' => '75,0,130',
+ 'ivory' => '255,255,240',
+ 'khaki' => '240,230,140',
+ 'lavender' => '230,230,250',
+ 'lavenderblush' => '255,240,245',
+ 'lawngreen' => '124,252,0',
+ 'lemonchiffon' => '255,250,205',
+ 'lightblue' => '173,216,230',
+ 'lightcoral' => '240,128,128',
+ 'lightcyan' => '224,255,255',
+ 'lightgoldenrodyellow' => '250,250,210',
+ 'lightgray' => '211,211,211',
+ 'lightgreen' => '144,238,144',
+ 'lightgrey' => '211,211,211',
+ 'lightpink' => '255,182,193',
+ 'lightsalmon' => '255,160,122',
+ 'lightseagreen' => '32,178,170',
+ 'lightskyblue' => '135,206,250',
+ 'lightslategray' => '119,136,153',
+ 'lightslategrey' => '119,136,153',
+ 'lightsteelblue' => '176,196,222',
+ 'lightyellow' => '255,255,224',
+ 'lime' => '0,255,0',
+ 'limegreen' => '50,205,50',
+ 'linen' => '250,240,230',
+ 'magenta' => '255,0,255',
+ 'maroon' => '128,0,0',
+ 'mediumaquamarine' => '102,205,170',
+ 'mediumblue' => '0,0,205',
+ 'mediumorchid' => '186,85,211',
+ 'mediumpurple' => '147,112,219',
+ 'mediumseagreen' => '60,179,113',
+ 'mediumslateblue' => '123,104,238',
+ 'mediumspringgreen' => '0,250,154',
+ 'mediumturquoise' => '72,209,204',
+ 'mediumvioletred' => '199,21,133',
+ 'midnightblue' => '25,25,112',
+ 'mintcream' => '245,255,250',
+ 'mistyrose' => '255,228,225',
+ 'moccasin' => '255,228,181',
+ 'navajowhite' => '255,222,173',
+ 'navy' => '0,0,128',
+ 'oldlace' => '253,245,230',
+ 'olive' => '128,128,0',
+ 'olivedrab' => '107,142,35',
+ 'orange' => '255,165,0',
+ 'orangered' => '255,69,0',
+ 'orchid' => '218,112,214',
+ 'palegoldenrod' => '238,232,170',
+ 'palegreen' => '152,251,152',
+ 'paleturquoise' => '175,238,238',
+ 'palevioletred' => '219,112,147',
+ 'papayawhip' => '255,239,213',
+ 'peachpuff' => '255,218,185',
+ 'peru' => '205,133,63',
+ 'pink' => '255,192,203',
+ 'plum' => '221,160,221',
+ 'powderblue' => '176,224,230',
+ 'purple' => '128,0,128',
+ 'red' => '255,0,0',
+ 'rosybrown' => '188,143,143',
+ 'royalblue' => '65,105,225',
+ 'saddlebrown' => '139,69,19',
+ 'salmon' => '250,128,114',
+ 'sandybrown' => '244,164,96',
+ 'seagreen' => '46,139,87',
+ 'seashell' => '255,245,238',
+ 'sienna' => '160,82,45',
+ 'silver' => '192,192,192',
+ 'skyblue' => '135,206,235',
+ 'slateblue' => '106,90,205',
+ 'slategray' => '112,128,144',
+ 'slategrey' => '112,128,144',
+ 'snow' => '255,250,250',
+ 'springgreen' => '0,255,127',
+ 'steelblue' => '70,130,180',
+ 'tan' => '210,180,140',
+ 'teal' => '0,128,128',
+ 'thistle' => '216,191,216',
+ 'tomato' => '255,99,71',
+ 'transparent' => '0,0,0,0',
+ 'turquoise' => '64,224,208',
+ 'violet' => '238,130,238',
+ 'wheat' => '245,222,179',
+ 'white' => '255,255,255',
+ 'whitesmoke' => '245,245,245',
+ 'yellow' => '255,255,0',
+ 'yellowgreen' => '154,205,50'
+ );
+}
+
+// responsible for taking a string of LESS code and converting it into a
+// syntax tree
+class lessc_parser {
+ static protected $nextBlockId = 0; // used to uniquely identify blocks
+
+ static protected $precedence = array(
+ '=<' => 0,
+ '>=' => 0,
+ '=' => 0,
+ '<' => 0,
+ '>' => 0,
+
+ '+' => 1,
+ '-' => 1,
+ '*' => 2,
+ '/' => 2,
+ '%' => 2,
+ );
+
+ static protected $whitePattern;
+ static protected $commentMulti;
+
+ static protected $commentSingle = "//";
+ static protected $commentMultiLeft = "/*";
+ static protected $commentMultiRight = "*/";
+
+ // regex string to match any of the operators
+ static protected $operatorString;
+
+ // these properties will supress division unless it's inside parenthases
+ static protected $supressDivisionProps =
+ array('/border-radius$/i', '/^font$/i');
+
+ protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
+ protected $lineDirectives = array("charset");
+
+ /**
+ * if we are in parens we can be more liberal with whitespace around
+ * operators because it must evaluate to a single value and thus is less
+ * ambiguous.
+ *
+ * Consider:
+ * property1: 10 -5; // is two numbers, 10 and -5
+ * property2: (10 -5); // should evaluate to 5
+ */
+ protected $inParens = false;
+
+ // caches preg escaped literals
+ static protected $literalCache = array();
+
+ public function __construct($lessc, $sourceName = null) {
+ $this->eatWhiteDefault = true;
+ // reference to less needed for vPrefix, mPrefix, and parentSelector
+ $this->lessc = $lessc;
+
+ $this->sourceName = $sourceName; // name used for error messages
+
+ $this->writeComments = false;
+
+ if (!self::$operatorString) {
+ self::$operatorString =
+ '('.implode('|', array_map(array('lessc', 'preg_quote'),
+ array_keys(self::$precedence))).')';
+
+ $commentSingle = lessc::preg_quote(self::$commentSingle);
+ $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
+ $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
+
+ self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
+ self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
+ }
+ }
+
+ public function parse($buffer) {
+ $this->count = 0;
+ $this->line = 1;
+
+ $this->env = null; // block stack
+ $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
+ $this->pushSpecialBlock("root");
+ $this->eatWhiteDefault = true;
+ $this->seenComments = array();
+
+ // trim whitespace on head
+ // if (preg_match('/^\s+/', $this->buffer, $m)) {
+ // $this->line += substr_count($m[0], "\n");
+ // $this->buffer = ltrim($this->buffer);
+ // }
+ $this->whitespace();
+
+ // parse the entire file
+ while (false !== $this->parseChunk());
+
+ if ($this->count != strlen($this->buffer))
+ $this->throwError();
+
+ // TODO report where the block was opened
+ if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
+ throw new exception('parse error: unclosed block');
+
+ return $this->env;
+ }
+
+ /**
+ * Parse a single chunk off the head of the buffer and append it to the
+ * current parse environment.
+ * Returns false when the buffer is empty, or when there is an error.
+ *
+ * This function is called repeatedly until the entire document is
+ * parsed.
+ *
+ * This parser is most similar to a recursive descent parser. Single
+ * functions represent discrete grammatical rules for the language, and
+ * they are able to capture the text that represents those rules.
+ *
+ * Consider the function lessc::keyword(). (all parse functions are
+ * structured the same)
+ *
+ * The function takes a single reference argument. When calling the
+ * function it will attempt to match a keyword on the head of the buffer.
+ * If it is successful, it will place the keyword in the referenced
+ * argument, advance the position in the buffer, and return true. If it
+ * fails then it won't advance the buffer and it will return false.
+ *
+ * All of these parse functions are powered by lessc::match(), which behaves
+ * the same way, but takes a literal regular expression. Sometimes it is
+ * more convenient to use match instead of creating a new function.
+ *
+ * Because of the format of the functions, to parse an entire string of
+ * grammatical rules, you can chain them together using &&.
+ *
+ * But, if some of the rules in the chain succeed before one fails, then
+ * the buffer position will be left at an invalid state. In order to
+ * avoid this, lessc::seek() is used to remember and set buffer positions.
+ *
+ * Before parsing a chain, use $s = $this->seek() to remember the current
+ * position into $s. Then if a chain fails, use $this->seek($s) to
+ * go back where we started.
+ */
+ protected function parseChunk() {
+ if (empty($this->buffer)) return false;
+ $s = $this->seek();
+
+ if ($this->whitespace()) {
+ return true;
+ }
+
+ // setting a property
+ if ($this->keyword($key) && $this->assign() &&
+ $this->propertyValue($value, $key) && $this->end())
+ {
+ $this->append(array('assign', $key, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+
+ // look for special css blocks
+ if ($this->literal('@', false)) {
+ $this->count--;
+
+ // media
+ if ($this->literal('@media')) {
+ if (($this->mediaQueryList($mediaQueries) || true)
+ && $this->literal('{'))
+ {
+ $media = $this->pushSpecialBlock("media");
+ $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
+ return true;
+ } else {
+ $this->seek($s);
+ return false;
+ }
+ }
+
+ if ($this->literal("@", false) && $this->keyword($dirName)) {
+ if ($this->isDirective($dirName, $this->blockDirectives)) {
+ if (($this->openString("{", $dirValue, null, array(";")) || true) &&
+ $this->literal("{"))
+ {
+ $dir = $this->pushSpecialBlock("directive");
+ $dir->name = $dirName;
+ if (isset($dirValue)) $dir->value = $dirValue;
+ return true;
+ }
+ } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
+ if ($this->propertyValue($dirValue) && $this->end()) {
+ $this->append(array("directive", $dirName, $dirValue));
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ }
+
+ // setting a variable
+ if ($this->variable($var) && $this->assign() &&
+ $this->propertyValue($value) && $this->end())
+ {
+ $this->append(array('assign', $var, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->import($importValue)) {
+ $this->append($importValue, $s);
+ return true;
+ }
+
+ // opening parametric mixin
+ if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
+ ($this->guards($guards) || true) &&
+ $this->literal('{'))
+ {
+ $block = $this->pushBlock($this->fixTags(array($tag)));
+ $block->args = $args;
+ $block->isVararg = $isVararg;
+ if (!empty($guards)) $block->guards = $guards;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // opening a simple block
+ if ($this->tags($tags) && $this->literal('{', false)) {
+ $tags = $this->fixTags($tags);
+ $this->pushBlock($tags);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // closing a block
+ if ($this->literal('}', false)) {
+ try {
+ $block = $this->pop();
+ } catch (exception $e) {
+ $this->seek($s);
+ $this->throwError($e->getMessage());
+ }
+
+ $hidden = false;
+ if (is_null($block->type)) {
+ $hidden = true;
+ if (!isset($block->args)) {
+ foreach ($block->tags as $tag) {
+ if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
+ $hidden = false;
+ break;
+ }
+ }
+ }
+
+ foreach ($block->tags as $tag) {
+ if (is_string($tag)) {
+ $this->env->children[$tag][] = $block;
+ }
+ }
+ }
+
+ if (!$hidden) {
+ $this->append(array('block', $block), $s);
+ }
+
+ // this is done here so comments aren't bundled into he block that
+ // was just closed
+ $this->whitespace();
+ return true;
+ }
+
+ // mixin
+ if ($this->mixinTags($tags) &&
+ ($this->argumentDef($argv, $isVararg) || true) &&
+ ($this->keyword($suffix) || true) && $this->end())
+ {
+ $tags = $this->fixTags($tags);
+ $this->append(array('mixin', $tags, $argv, $suffix), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // spare ;
+ if ($this->literal(';')) return true;
+
+ return false; // got nothing, throw error
+ }
+
+ protected function isDirective($dirname, $directives) {
+ // TODO: cache pattern in parser
+ $pattern = implode("|",
+ array_map(array("lessc", "preg_quote"), $directives));
+ $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
+
+ return preg_match($pattern, $dirname);
+ }
+
+ protected function fixTags($tags) {
+ // move @ tags out of variable namespace
+ foreach ($tags as &$tag) {
+ if ($tag{0} == $this->lessc->vPrefix)
+ $tag[0] = $this->lessc->mPrefix;
+ }
+ return $tags;
+ }
+
+ // a list of expressions
+ protected function expressionList(&$exps) {
+ $values = array();
+
+ while ($this->expression($exp)) {
+ $values[] = $exp;
+ }
+
+ if (count($values) == 0) return false;
+
+ $exps = lessc::compressList($values, ' ');
+ return true;
+ }
+
+ /**
+ * Attempt to consume an expression.
+ * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
+ */
+ protected function expression(&$out) {
+ if ($this->value($lhs)) {
+ $out = $this->expHelper($lhs, 0);
+
+ // look for / shorthand
+ if (!empty($this->env->supressedDivision)) {
+ unset($this->env->supressedDivision);
+ $s = $this->seek();
+ if ($this->literal("/") && $this->value($rhs)) {
+ $out = array("list", "",
+ array($out, array("keyword", "/"), $rhs));
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * recursively parse infix equation with $lhs at precedence $minP
+ */
+ protected function expHelper($lhs, $minP) {
+ $this->inExp = true;
+ $ss = $this->seek();
+
+ while (true) {
+ $whiteBefore = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ // If there is whitespace before the operator, then we require
+ // whitespace after the operator for it to be an expression
+ $needWhite = $whiteBefore && !$this->inParens;
+
+ if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
+ if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
+ foreach (self::$supressDivisionProps as $pattern) {
+ if (preg_match($pattern, $this->env->currentProperty)) {
+ $this->env->supressedDivision = true;
+ break 2;
+ }
+ }
+ }
+
+
+ $whiteAfter = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ if (!$this->value($rhs)) break;
+
+ // peek for next operator to see what to do with rhs
+ if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
+ $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
+ }
+
+ $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
+ $ss = $this->seek();
+
+ continue;
+ }
+
+ break;
+ }
+
+ $this->seek($ss);
+
+ return $lhs;
+ }
+
+ // consume a list of values for a property
+ public function propertyValue(&$value, $keyName = null) {
+ $values = array();
+
+ if ($keyName !== null) $this->env->currentProperty = $keyName;
+
+ $s = null;
+ while ($this->expressionList($v)) {
+ $values[] = $v;
+ $s = $this->seek();
+ if (!$this->literal(',')) break;
+ }
+
+ if ($s) $this->seek($s);
+
+ if ($keyName !== null) unset($this->env->currentProperty);
+
+ if (count($values) == 0) return false;
+
+ $value = lessc::compressList($values, ', ');
+ return true;
+ }
+
+ protected function parenValue(&$out) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
+ return false;
+ }
+
+ $inParens = $this->inParens;
+ if ($this->literal("(") &&
+ ($this->inParens = true) && $this->expression($exp) &&
+ $this->literal(")"))
+ {
+ $out = $exp;
+ $this->inParens = $inParens;
+ return true;
+ } else {
+ $this->inParens = $inParens;
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // a single value
+ protected function value(&$value) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
+ // negation
+ if ($this->literal("-", false) &&
+ (($this->variable($inner) && $inner = array("variable", $inner)) ||
+ $this->unit($inner) ||
+ $this->parenValue($inner)))
+ {
+ $value = array("unary", "-", $inner);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ if ($this->parenValue($value)) return true;
+ if ($this->unit($value)) return true;
+ if ($this->color($value)) return true;
+ if ($this->func($value)) return true;
+ if ($this->string($value)) return true;
+
+ if ($this->keyword($word)) {
+ $value = array('keyword', $word);
+ return true;
+ }
+
+ // try a variable
+ if ($this->variable($var)) {
+ $value = array('variable', $var);
+ return true;
+ }
+
+ // unquote string (should this work on any type?
+ if ($this->literal("~") && $this->string($str)) {
+ $value = array("escape", $str);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // css hack: \0
+ if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
+ $value = array('keyword', '\\'.$m[1]);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // an import statement
+ protected function import(&$out) {
+ if (!$this->literal('@import')) return false;
+
+ // @import "something.css" media;
+ // @import url("something.css") media;
+ // @import url(something.css) media;
+
+ if ($this->propertyValue($value)) {
+ $out = array("import", $value);
+ return true;
+ }
+ }
+
+ protected function mediaQueryList(&$out) {
+ if ($this->genericList($list, "mediaQuery", ",", false)) {
+ $out = $list[2];
+ return true;
+ }
+ return false;
+ }
+
+ protected function mediaQuery(&$out) {
+ $s = $this->seek();
+
+ $expressions = null;
+ $parts = array();
+
+ if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
+ $prop = array("mediaType");
+ if (isset($only)) $prop[] = "only";
+ if (isset($not)) $prop[] = "not";
+ $prop[] = $mediaType;
+ $parts[] = $prop;
+ } else {
+ $this->seek($s);
+ }
+
+
+ if (!empty($mediaType) && !$this->literal("and")) {
+ // ~
+ } else {
+ $this->genericList($expressions, "mediaExpression", "and", false);
+ if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
+ }
+
+ if (count($parts) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $parts;
+ return true;
+ }
+
+ protected function mediaExpression(&$out) {
+ $s = $this->seek();
+ $value = null;
+ if ($this->literal("(") &&
+ $this->keyword($feature) &&
+ ($this->literal(":") && $this->expression($value) || true) &&
+ $this->literal(")"))
+ {
+ $out = array("mediaExp", $feature);
+ if ($value) $out[] = $value;
+ return true;
+ } elseif ($this->variable($variable)) {
+ $out = array('variable', $variable);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // an unbounded string stopped by $end
+ protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ $stop = array("'", '"', "@{", $end);
+ $stop = array_map(array("lessc", "preg_quote"), $stop);
+ // $stop[] = self::$commentMulti;
+
+ if (!is_null($rejectStrs)) {
+ $stop = array_merge($stop, $rejectStrs);
+ }
+
+ $patt = '(.*?)('.implode("|", $stop).')';
+
+ $nestingLevel = 0;
+
+ $content = array();
+ while ($this->match($patt, $m, false)) {
+ if (!empty($m[1])) {
+ $content[] = $m[1];
+ if ($nestingOpen) {
+ $nestingLevel += substr_count($m[1], $nestingOpen);
+ }
+ }
+
+ $tok = $m[2];
+
+ $this->count-= strlen($tok);
+ if ($tok == $end) {
+ if ($nestingLevel == 0) {
+ break;
+ } else {
+ $nestingLevel--;
+ }
+ }
+
+ if (($tok == "'" || $tok == '"') && $this->string($str)) {
+ $content[] = $str;
+ continue;
+ }
+
+ if ($tok == "@{" && $this->interpolation($inter)) {
+ $content[] = $inter;
+ continue;
+ }
+
+ if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
+ break;
+ }
+
+ $content[] = $tok;
+ $this->count+= strlen($tok);
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($content) == 0) return false;
+
+ // trim the end
+ if (is_string(end($content))) {
+ $content[count($content) - 1] = rtrim(end($content));
+ }
+
+ $out = array("string", "", $content);
+ return true;
+ }
+
+ protected function string(&$out) {
+ $s = $this->seek();
+ if ($this->literal('"', false)) {
+ $delim = '"';
+ } elseif ($this->literal("'", false)) {
+ $delim = "'";
+ } else {
+ return false;
+ }
+
+ $content = array();
+
+ // look for either ending delim , escape, or string interpolation
+ $patt = '([^\n]*?)(@\{|\\\\|' .
+ lessc::preg_quote($delim).')';
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while ($this->match($patt, $m, false)) {
+ $content[] = $m[1];
+ if ($m[2] == "@{") {
+ $this->count -= strlen($m[2]);
+ if ($this->interpolation($inter, false)) {
+ $content[] = $inter;
+ } else {
+ $this->count += strlen($m[2]);
+ $content[] = "@{"; // ignore it
+ }
+ } elseif ($m[2] == '\\') {
+ $content[] = $m[2];
+ if ($this->literal($delim, false)) {
+ $content[] = $delim;
+ }
+ } else {
+ $this->count -= strlen($delim);
+ break; // delim
+ }
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if ($this->literal($delim)) {
+ $out = array("string", $delim, $content);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function interpolation(&$out) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = true;
+
+ $s = $this->seek();
+ if ($this->literal("@{") &&
+ $this->openString("}", $interp, null, array("'", '"', ";")) &&
+ $this->literal("}", false))
+ {
+ $out = array("interpolate", $interp);
+ $this->eatWhiteDefault = $oldWhite;
+ if ($this->eatWhiteDefault) $this->whitespace();
+ return true;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ $this->seek($s);
+ return false;
+ }
+
+ protected function unit(&$unit) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count])) {
+ $char = $this->buffer[$this->count];
+ if (!ctype_digit($char) && $char != ".") return false;
+ }
+
+ if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
+ $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
+ return true;
+ }
+ return false;
+ }
+
+ // a # color
+ protected function color(&$out) {
+ if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
+ if (strlen($m[1]) > 7) {
+ $out = array("string", "", array($m[1]));
+ } else {
+ $out = array("raw_color", $m[1]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ // consume an argument definition list surrounded by ()
+ // each argument is a variable name with optional value
+ // or at the end a ... or a variable named followed by ...
+ // arguments are separated by , unless a ; is in the list, then ; is the
+ // delimiter.
+ protected function argumentDef(&$args, &$isVararg) {
+ $s = $this->seek();
+ if (!$this->literal('(')) return false;
+
+ $values = array();
+ $delim = ",";
+ $method = "expressionList";
+
+ $isVararg = false;
+ while (true) {
+ if ($this->literal("...")) {
+ $isVararg = true;
+ break;
+ }
+
+ if ($this->$method($value)) {
+ if ($value[0] == "variable") {
+ $arg = array("arg", $value[1]);
+ $ss = $this->seek();
+
+ if ($this->assign() && $this->$method($rhs)) {
+ $arg[] = $rhs;
+ } else {
+ $this->seek($ss);
+ if ($this->literal("...")) {
+ $arg[0] = "rest";
+ $isVararg = true;
+ }
+ }
+
+ $values[] = $arg;
+ if ($isVararg) break;
+ continue;
+ } else {
+ $values[] = array("lit", $value);
+ }
+ }
+
+
+ if (!$this->literal($delim)) {
+ if ($delim == "," && $this->literal(";")) {
+ // found new delim, convert existing args
+ $delim = ";";
+ $method = "propertyValue";
+
+ // transform arg list
+ if (isset($values[1])) { // 2 items
+ $newList = array();
+ foreach ($values as $i => $arg) {
+ switch($arg[0]) {
+ case "arg":
+ if ($i) {
+ $this->throwError("Cannot mix ; and , as delimiter types");
+ }
+ $newList[] = $arg[2];
+ break;
+ case "lit":
+ $newList[] = $arg[1];
+ break;
+ case "rest":
+ $this->throwError("Unexpected rest before semicolon");
+ }
+ }
+
+ $newList = array("list", ", ", $newList);
+
+ switch ($values[0][0]) {
+ case "arg":
+ $newArg = array("arg", $values[0][1], $newList);
+ break;
+ case "lit":
+ $newArg = array("lit", $newList);
+ break;
+ }
+
+ } elseif ($values) { // 1 item
+ $newArg = $values[0];
+ }
+
+ if ($newArg) {
+ $values = array($newArg);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (!$this->literal(')')) {
+ $this->seek($s);
+ return false;
+ }
+
+ $args = $values;
+
+ return true;
+ }
+
+ // consume a list of tags
+ // this accepts a hanging delimiter
+ protected function tags(&$tags, $simple = false, $delim = ',') {
+ $tags = array();
+ while ($this->tag($tt, $simple)) {
+ $tags[] = $tt;
+ if (!$this->literal($delim)) break;
+ }
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // list of tags of specifying mixin path
+ // optionally separated by > (lazy, accepts extra >)
+ protected function mixinTags(&$tags) {
+ $tags = array();
+ while ($this->tag($tt, true)) {
+ $tags[] = $tt;
+ $this->literal(">");
+ }
+
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // a bracketed value (contained within in a tag definition)
+ protected function tagBracket(&$parts, &$hasExpression) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
+ return false;
+ }
+
+ $s = $this->seek();
+
+ $hasInterpolation = false;
+
+ if ($this->literal("[", false)) {
+ $attrParts = array("[");
+ // keyword, string, operator
+ while (true) {
+ if ($this->literal("]", false)) {
+ $this->count--;
+ break; // get out early
+ }
+
+ if ($this->match('\s+', $m)) {
+ $attrParts[] = " ";
+ continue;
+ }
+ if ($this->string($str)) {
+ // escape parent selector, (yuck)
+ foreach ($str[2] as &$chunk) {
+ $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
+ }
+
+ $attrParts[] = $str;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ if ($this->keyword($word)) {
+ $attrParts[] = $word;
+ continue;
+ }
+
+ if ($this->interpolation($inter, false)) {
+ $attrParts[] = $inter;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ // operator, handles attr namespace too
+ if ($this->match('[|-~\$\*\^=]+', $m)) {
+ $attrParts[] = $m[0];
+ continue;
+ }
+
+ break;
+ }
+
+ if ($this->literal("]", false)) {
+ $attrParts[] = "]";
+ foreach ($attrParts as $part) {
+ $parts[] = $part;
+ }
+ $hasExpression = $hasExpression || $hasInterpolation;
+ return true;
+ }
+ $this->seek($s);
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // a space separated list of selectors
+ protected function tag(&$tag, $simple = false) {
+ if ($simple)
+ $chars = '^@,:;{}\][>\(\) "\'';
+ else
+ $chars = '^@,;{}["\'';
+
+ $s = $this->seek();
+
+ $hasExpression = false;
+ $parts = array();
+ while ($this->tagBracket($parts, $hasExpression));
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while (true) {
+ if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
+ $parts[] = $m[1];
+ if ($simple) break;
+
+ while ($this->tagBracket($parts, $hasExpression));
+ continue;
+ }
+
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
+ if ($this->interpolation($interp)) {
+ $hasExpression = true;
+ $interp[2] = true; // don't unescape
+ $parts[] = $interp;
+ continue;
+ }
+
+ if ($this->literal("@")) {
+ $parts[] = "@";
+ continue;
+ }
+ }
+
+ if ($this->unit($unit)) { // for keyframes
+ $parts[] = $unit[1];
+ $parts[] = $unit[2];
+ continue;
+ }
+
+ break;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ if (!$parts) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($hasExpression) {
+ $tag = array("exp", array("string", "", $parts));
+ } else {
+ $tag = trim(implode($parts));
+ }
+
+ $this->whitespace();
+ return true;
+ }
+
+ // a css function
+ protected function func(&$func) {
+ $s = $this->seek();
+
+ if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
+ $fname = $m[1];
+
+ $sPreArgs = $this->seek();
+
+ $args = array();
+ while (true) {
+ $ss = $this->seek();
+ // this ugly nonsense is for ie filter properties
+ if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
+ $args[] = array("string", "", array($name, "=", $value));
+ } else {
+ $this->seek($ss);
+ if ($this->expressionList($value)) {
+ $args[] = $value;
+ }
+ }
+
+ if (!$this->literal(',')) break;
+ }
+ $args = array('list', ',', $args);
+
+ if ($this->literal(')')) {
+ $func = array('function', $fname, $args);
+ return true;
+ } elseif ($fname == 'url') {
+ // couldn't parse and in url? treat as string
+ $this->seek($sPreArgs);
+ if ($this->openString(")", $string) && $this->literal(")")) {
+ $func = array('function', $fname, $string);
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // consume a less variable
+ protected function variable(&$name) {
+ $s = $this->seek();
+ if ($this->literal($this->lessc->vPrefix, false) &&
+ ($this->variable($sub) || $this->keyword($name)))
+ {
+ if (!empty($sub)) {
+ $name = array('variable', $sub);
+ } else {
+ $name = $this->lessc->vPrefix.$name;
+ }
+ return true;
+ }
+
+ $name = null;
+ $this->seek($s);
+ return false;
+ }
+
+ /**
+ * Consume an assignment operator
+ * Can optionally take a name that will be set to the current property name
+ */
+ protected function assign($name = null) {
+ if ($name) $this->currentProperty = $name;
+ return $this->literal(':') || $this->literal('=');
+ }
+
+ // consume a keyword
+ protected function keyword(&$word) {
+ if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
+ $word = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ // consume an end of statement delimiter
+ protected function end() {
+ if ($this->literal(';', false)) {
+ return true;
+ } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
+ // if there is end of file or a closing block next then we don't need a ;
+ return true;
+ }
+ return false;
+ }
+
+ protected function guards(&$guards) {
+ $s = $this->seek();
+
+ if (!$this->literal("when")) {
+ $this->seek($s);
+ return false;
+ }
+
+ $guards = array();
+
+ while ($this->guardGroup($g)) {
+ $guards[] = $g;
+ if (!$this->literal(",")) break;
+ }
+
+ if (count($guards) == 0) {
+ $guards = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ // a bunch of guards that are and'd together
+ // TODO rename to guardGroup
+ protected function guardGroup(&$guardGroup) {
+ $s = $this->seek();
+ $guardGroup = array();
+ while ($this->guard($guard)) {
+ $guardGroup[] = $guard;
+ if (!$this->literal("and")) break;
+ }
+
+ if (count($guardGroup) == 0) {
+ $guardGroup = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function guard(&$guard) {
+ $s = $this->seek();
+ $negate = $this->literal("not");
+
+ if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
+ $guard = $exp;
+ if ($negate) $guard = array("negate", $guard);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ /* raw parsing functions */
+
+ protected function literal($what, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ // shortcut on single letter
+ if (!isset($what[1]) && isset($this->buffer[$this->count])) {
+ if ($this->buffer[$this->count] == $what) {
+ if (!$eatWhitespace) {
+ $this->count++;
+ return true;
+ }
+ // goes below...
+ } else {
+ return false;
+ }
+ }
+
+ if (!isset(self::$literalCache[$what])) {
+ self::$literalCache[$what] = lessc::preg_quote($what);
+ }
+
+ return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
+ }
+
+ protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
+ $s = $this->seek();
+ $items = array();
+ while ($this->$parseItem($value)) {
+ $items[] = $value;
+ if ($delim) {
+ if (!$this->literal($delim)) break;
+ }
+ }
+
+ if (count($items) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($flatten && count($items) == 1) {
+ $out = $items[0];
+ } else {
+ $out = array("list", $delim, $items);
+ }
+
+ return true;
+ }
+
+
+ // advance counter to next occurrence of $what
+ // $until - don't include $what in advance
+ // $allowNewline, if string, will be used as valid char set
+ protected function to($what, &$out, $until = false, $allowNewline = false) {
+ if (is_string($allowNewline)) {
+ $validChars = $allowNewline;
+ } else {
+ $validChars = $allowNewline ? "." : "[^\n]";
+ }
+ if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
+ if ($until) $this->count -= strlen($what); // give back $what
+ $out = $m[1];
+ return true;
+ }
+
+ // try to match something on head of buffer
+ protected function match($regex, &$out, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+ $this->count += strlen($out[0]);
+ if ($eatWhitespace && $this->writeComments) $this->whitespace();
+ return true;
+ }
+ return false;
+ }
+
+ // match some whitespace
+ protected function whitespace() {
+ if ($this->writeComments) {
+ $gotWhite = false;
+ while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
+ if (isset($m[1]) && empty($this->seenComments[$this->count])) {
+ $this->append(array("comment", $m[1]));
+ $this->seenComments[$this->count] = true;
+ }
+ $this->count += strlen($m[0]);
+ $gotWhite = true;
+ }
+ return $gotWhite;
+ } else {
+ $this->match("", $m);
+ return strlen($m[0]) > 0;
+ }
+ }
+
+ // match something without consuming it
+ protected function peek($regex, &$out = null, $from=null) {
+ if (is_null($from)) $from = $this->count;
+ $r = '/'.$regex.'/Ais';
+ $result = preg_match($r, $this->buffer, $out, null, $from);
+
+ return $result;
+ }
+
+ // seek to a spot in the buffer or return where we are on no argument
+ protected function seek($where = null) {
+ if ($where === null) return $this->count;
+ else $this->count = $where;
+ return true;
+ }
+
+ /* misc functions */
+
+ public function throwError($msg = "parse error", $count = null) {
+ $count = is_null($count) ? $this->count : $count;
+
+ $line = $this->line +
+ substr_count(substr($this->buffer, 0, $count), "\n");
+
+ if (!empty($this->sourceName)) {
+ $loc = "$this->sourceName on line $line";
+ } else {
+ $loc = "line: $line";
+ }
+
+ // TODO this depends on $this->count
+ if ($this->peek("(.*?)(\n|$)", $m, $count)) {
+ throw new exception("$msg: failed at `$m[1]` $loc");
+ } else {
+ throw new exception("$msg: $loc");
+ }
+ }
+
+ protected function pushBlock($selectors=null, $type=null) {
+ $b = new stdclass;
+ $b->parent = $this->env;
+
+ $b->type = $type;
+ $b->id = self::$nextBlockId++;
+
+ $b->isVararg = false; // TODO: kill me from here
+ $b->tags = $selectors;
+
+ $b->props = array();
+ $b->children = array();
+
+ $this->env = $b;
+ return $b;
+ }
+
+ // push a block that doesn't multiply tags
+ protected function pushSpecialBlock($type) {
+ return $this->pushBlock(null, $type);
+ }
+
+ // append a property to the current block
+ protected function append($prop, $pos = null) {
+ if ($pos !== null) $prop[-1] = $pos;
+ $this->env->props[] = $prop;
+ }
+
+ // pop something off the stack
+ protected function pop() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // remove comments from $text
+ // todo: make it work for all functions, not just url
+ protected function removeComments($text) {
+ $look = array(
+ 'url(', '//', '/*', '"', "'"
+ );
+
+ $out = '';
+ $min = null;
+ while (true) {
+ // find the next item
+ foreach ($look as $token) {
+ $pos = strpos($text, $token);
+ if ($pos !== false) {
+ if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
+ }
+ }
+
+ if (is_null($min)) break;
+
+ $count = $min[1];
+ $skip = 0;
+ $newlines = 0;
+ switch ($min[0]) {
+ case 'url(':
+ if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - strlen($min[0]);
+ break;
+ case '"':
+ case "'":
+ if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - 1;
+ break;
+ case '//':
+ $skip = strpos($text, "\n", $count);
+ if ($skip === false) $skip = strlen($text) - $count;
+ else $skip -= $count;
+ break;
+ case '/*':
+ if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
+ $skip = strlen($m[0]);
+ $newlines = substr_count($m[0], "\n");
+ }
+ break;
+ }
+
+ if ($skip == 0) $count += strlen($min[0]);
+
+ $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
+ $text = substr($text, $count + $skip);
+
+ $min = null;
+ }
+
+ return $out.$text;
+ }
+
+}
+
+class lessc_formatter_classic {
+ public $indentChar = " ";
+
+ public $break = "\n";
+ public $open = " {";
+ public $close = "}";
+ public $selectorSeparator = ", ";
+ public $assignSeparator = ":";
+
+ public $openSingle = " { ";
+ public $closeSingle = " }";
+
+ public $disableSingle = false;
+ public $breakSelectors = false;
+
+ public $compressColors = false;
+
+ public function __construct() {
+ $this->indentLevel = 0;
+ }
+
+ public function indentStr($n = 0) {
+ return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
+ }
+
+ public function property($name, $value) {
+ return $name . $this->assignSeparator . $value . ";";
+ }
+
+ protected function isEmpty($block) {
+ if (empty($block->lines)) {
+ foreach ($block->children as $child) {
+ if (!$this->isEmpty($child)) return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ public function block($block) {
+ if ($this->isEmpty($block)) return;
+
+ $inner = $pre = $this->indentStr();
+
+ $isSingle = !$this->disableSingle &&
+ is_null($block->type) && count($block->lines) == 1;
+
+ if (!empty($block->selectors)) {
+ $this->indentLevel++;
+
+ if ($this->breakSelectors) {
+ $selectorSeparator = $this->selectorSeparator . $this->break . $pre;
+ } else {
+ $selectorSeparator = $this->selectorSeparator;
+ }
+
+ echo $pre .
+ implode($selectorSeparator, $block->selectors);
+ if ($isSingle) {
+ echo $this->openSingle;
+ $inner = "";
+ } else {
+ echo $this->open . $this->break;
+ $inner = $this->indentStr();
+ }
+
+ }
+
+ if (!empty($block->lines)) {
+ $glue = $this->break.$inner;
+ echo $inner . implode($glue, $block->lines);
+ if (!$isSingle && !empty($block->children)) {
+ echo $this->break;
+ }
+ }
+
+ foreach ($block->children as $child) {
+ $this->block($child);
+ }
+
+ if (!empty($block->selectors)) {
+ if (!$isSingle && empty($block->children)) echo $this->break;
+
+ if ($isSingle) {
+ echo $this->closeSingle . $this->break;
+ } else {
+ echo $pre . $this->close . $this->break;
+ }
+
+ $this->indentLevel--;
+ }
+ }
+}
+
+class lessc_formatter_compressed extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $open = "{";
+ public $selectorSeparator = ",";
+ public $assignSeparator = ":";
+ public $break = "";
+ public $compressColors = true;
+
+ public function indentStr($n = 0) {
+ return "";
+ }
+}
+
+class lessc_formatter_lessjs extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $breakSelectors = true;
+ public $assignSeparator = ": ";
+ public $selectorSeparator = ",";
+}
+
diff --git a/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php
new file mode 100644
index 00000000..344b55f1
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/custom-css/preprocessors/scss.inc.php
@@ -0,0 +1,4383 @@
+<?php
+/**
+ * SCSS compiler written in PHP
+ *
+ * @copyright 2012-2013 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/gpl-license GPL-3.0
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://leafo.net/scssphp
+ */
+
+/**
+ * The scss compiler and parser.
+ *
+ * Converting SCSS to CSS is a three stage process. The incoming file is parsed
+ * by `scssc_parser` into a syntax tree, then it is compiled into another tree
+ * representing the CSS structure by `scssc`. The CSS tree is fed into a
+ * formatter, like `scssc_formatter` which then outputs CSS as a string.
+ *
+ * During the first compile, all values are *reduced*, which means that their
+ * types are brought to the lowest form before being dump as strings. This
+ * handles math equations, variable dereferences, and the like.
+ *
+ * The `parse` function of `scssc` is the entry point.
+ *
+ * In summary:
+ *
+ * The `scssc` class creates an instance of the parser, feeds it SCSS code,
+ * then transforms the resulting tree to a CSS tree. This class also holds the
+ * evaluation context, such as all available mixins and variables at any given
+ * time.
+ *
+ * The `scssc_parser` class is only concerned with parsing its input.
+ *
+ * The `scssc_formatter` takes a CSS tree, and dumps it to a formatted string,
+ * handling things like indentation.
+ */
+
+/**
+ * SCSS compiler
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scssc {
+ static public $VERSION = "v0.0.9";
+
+ static protected $operatorNames = array(
+ '+' => "add",
+ '-' => "sub",
+ '*' => "mul",
+ '/' => "div",
+ '%' => "mod",
+
+ '==' => "eq",
+ '!=' => "neq",
+ '<' => "lt",
+ '>' => "gt",
+
+ '<=' => "lte",
+ '>=' => "gte",
+ );
+
+ static protected $namespaces = array(
+ "special" => "%",
+ "mixin" => "@",
+ "function" => "^",
+ );
+
+ static protected $unitTable = array(
+ "in" => array(
+ "in" => 1,
+ "pt" => 72,
+ "pc" => 6,
+ "cm" => 2.54,
+ "mm" => 25.4,
+ "px" => 96,
+ )
+ );
+
+ static public $true = array("keyword", "true");
+ static public $false = array("keyword", "false");
+ static public $null = array("null");
+
+ static public $defaultValue = array("keyword", "");
+ static public $selfSelector = array("self");
+
+ protected $importPaths = array("");
+ protected $importCache = array();
+
+ protected $userFunctions = array();
+
+ protected $numberPrecision = 5;
+
+ protected $formatter = "scss_formatter_nested";
+
+ public function compile($code, $name=null) {
+ $this->indentLevel = -1;
+ $this->commentsSeen = array();
+ $this->extends = array();
+ $this->extendsMap = array();
+
+ $locale = setlocale(LC_NUMERIC, 0);
+ setlocale(LC_NUMERIC, "C");
+
+ $this->parsedFiles = array();
+ $this->parser = new scss_parser($name);
+ $tree = $this->parser->parse($code);
+
+ $this->formatter = new $this->formatter();
+
+ $this->env = null;
+ $this->scope = null;
+
+ $this->compileRoot($tree);
+
+ $out = $this->formatter->format($this->scope);
+
+ setlocale(LC_NUMERIC, $locale);
+ return $out;
+ }
+
+ protected function isSelfExtend($target, $origin) {
+ foreach ($origin as $sel) {
+ if (in_array($target, $sel)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function pushExtends($target, $origin) {
+ if ($this->isSelfExtend($target, $origin)) {
+ return;
+ }
+
+ $i = count($this->extends);
+ $this->extends[] = array($target, $origin);
+
+ foreach ($target as $part) {
+ if (isset($this->extendsMap[$part])) {
+ $this->extendsMap[$part][] = $i;
+ } else {
+ $this->extendsMap[$part] = array($i);
+ }
+ }
+ }
+
+ protected function makeOutputBlock($type, $selectors = null) {
+ $out = new stdClass;
+ $out->type = $type;
+ $out->lines = array();
+ $out->children = array();
+ $out->parent = $this->scope;
+ $out->selectors = $selectors;
+ $out->depth = $this->env->depth;
+
+ return $out;
+ }
+
+ protected function matchExtendsSingle($single, &$outOrigin) {
+ $counts = array();
+ foreach ($single as $part) {
+ if (!is_string($part)) return false; // hmm
+
+ if (isset($this->extendsMap[$part])) {
+ foreach ($this->extendsMap[$part] as $idx) {
+ $counts[$idx] =
+ isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
+ }
+ }
+ }
+
+ $outOrigin = array();
+ $found = false;
+
+ foreach ($counts as $idx => $count) {
+ list($target, $origin) = $this->extends[$idx];
+
+ // check count
+ if ($count != count($target)) continue;
+
+ // check if target is subset of single
+ if (array_diff(array_intersect($single, $target), $target)) continue;
+
+ $rem = array_diff($single, $target);
+
+ foreach ($origin as $j => $new) {
+ // prevent infinite loop when target extends itself
+ foreach ($new as $new_selector) {
+ if (!array_diff($single, $new_selector)) {
+ continue 2;
+ }
+ }
+
+ $origin[$j][count($origin[$j]) - 1] = $this->combineSelectorSingle(end($new), $rem);
+ }
+
+ $outOrigin = array_merge($outOrigin, $origin);
+
+ $found = true;
+ }
+
+ return $found;
+ }
+
+ protected function combineSelectorSingle($base, $other) {
+ $tag = null;
+ $out = array();
+
+ foreach (array($base, $other) as $single) {
+ foreach ($single as $part) {
+ if (preg_match('/^[^\[.#:]/', $part)) {
+ $tag = $part;
+ } else {
+ $out[] = $part;
+ }
+ }
+ }
+
+ if ($tag) {
+ array_unshift($out, $tag);
+ }
+
+ return $out;
+ }
+
+ protected function matchExtends($selector, &$out, $from = 0, $initial=true) {
+ foreach ($selector as $i => $part) {
+ if ($i < $from) continue;
+
+ if ($this->matchExtendsSingle($part, $origin)) {
+ $before = array_slice($selector, 0, $i);
+ $after = array_slice($selector, $i + 1);
+
+ foreach ($origin as $new) {
+ $k = 0;
+
+ // remove shared parts
+ if ($initial) {
+ foreach ($before as $k => $val) {
+ if (!isset($new[$k]) || $val != $new[$k]) {
+ break;
+ }
+ }
+ }
+
+ $result = array_merge(
+ $before,
+ $k > 0 ? array_slice($new, $k) : $new,
+ $after);
+
+
+ if ($result == $selector) continue;
+ $out[] = $result;
+
+ // recursively check for more matches
+ $this->matchExtends($result, $out, $i, false);
+
+ // selector sequence merging
+ if (!empty($before) && count($new) > 1) {
+ $result2 = array_merge(
+ array_slice($new, 0, -1),
+ $k > 0 ? array_slice($before, $k) : $before,
+ array_slice($new, -1),
+ $after);
+
+ $out[] = $result2;
+ }
+ }
+ }
+ }
+ }
+
+ protected function flattenSelectors($block, $parentKey = null) {
+ if ($block->selectors) {
+ $selectors = array();
+ foreach ($block->selectors as $s) {
+ $selectors[] = $s;
+ if (!is_array($s)) continue;
+ // check extends
+ if (!empty($this->extendsMap)) {
+ $this->matchExtends($s, $selectors);
+ }
+ }
+
+ $block->selectors = array();
+ $placeholderSelector = false;
+ foreach ($selectors as $selector) {
+ if ($this->hasSelectorPlaceholder($selector)) {
+ $placeholderSelector = true;
+ continue;
+ }
+ $block->selectors[] = $this->compileSelector($selector);
+ }
+
+ if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) {
+ unset($block->parent->children[$parentKey]);
+ return;
+ }
+ }
+
+ foreach ($block->children as $key => $child) {
+ $this->flattenSelectors($child, $key);
+ }
+ }
+
+ protected function compileRoot($rootBlock) {
+ $this->pushEnv($rootBlock);
+ $this->scope = $this->makeOutputBlock("root");
+
+ $this->compileChildren($rootBlock->children, $this->scope);
+ $this->flattenSelectors($this->scope);
+
+ $this->popEnv();
+ }
+
+ protected function compileMedia($media) {
+ $this->pushEnv($media);
+ $parentScope = $this->mediaParent($this->scope);
+
+ $this->scope = $this->makeOutputBlock("media", array(
+ $this->compileMediaQuery($this->multiplyMedia($this->env)))
+ );
+
+ $parentScope->children[] = $this->scope;
+
+ // top level properties in a media cause it to be wrapped
+ $needsWrap = false;
+ foreach ($media->children as $child) {
+ $type = $child[0];
+ if ($type !== 'block' && $type !== 'media' && $type !== 'directive') {
+ $needsWrap = true;
+ break;
+ }
+ }
+
+ if ($needsWrap) {
+ $wrapped = (object)array(
+ "selectors" => array(),
+ "children" => $media->children
+ );
+ $media->children = array(array("block", $wrapped));
+ }
+
+ $this->compileChildren($media->children, $this->scope);
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function mediaParent($scope) {
+ while (!empty($scope->parent)) {
+ if (!empty($scope->type) && $scope->type != "media") {
+ break;
+ }
+ $scope = $scope->parent;
+ }
+
+ return $scope;
+ }
+
+ // TODO refactor compileNestedBlock and compileMedia into same thing
+ protected function compileNestedBlock($block, $selectors) {
+ $this->pushEnv($block);
+
+ $this->scope = $this->makeOutputBlock($block->type, $selectors);
+ $this->scope->parent->children[] = $this->scope;
+ $this->compileChildren($block->children, $this->scope);
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ /**
+ * Recursively compiles a block.
+ *
+ * A block is analogous to a CSS block in most cases. A single SCSS document
+ * is encapsulated in a block when parsed, but it does not have parent tags
+ * so all of its children appear on the root level when compiled.
+ *
+ * Blocks are made up of selectors and children.
+ *
+ * The children of a block are just all the blocks that are defined within.
+ *
+ * Compiling the block involves pushing a fresh environment on the stack,
+ * and iterating through the props, compiling each one.
+ *
+ * @see scss::compileChild()
+ *
+ * @param \StdClass $block
+ */
+ protected function compileBlock($block) {
+ $env = $this->pushEnv($block);
+
+ $env->selectors =
+ array_map(array($this, "evalSelector"), $block->selectors);
+
+ $out = $this->makeOutputBlock(null, $this->multiplySelectors($env));
+ $this->scope->children[] = $out;
+ $this->compileChildren($block->children, $out);
+
+ $this->popEnv();
+ }
+
+ // joins together .classes and #ids
+ protected function flattenSelectorSingle($single) {
+ $joined = array();
+ foreach ($single as $part) {
+ if (empty($joined) ||
+ !is_string($part) ||
+ preg_match('/[\[.:#%]/', $part))
+ {
+ $joined[] = $part;
+ continue;
+ }
+
+ if (is_array(end($joined))) {
+ $joined[] = $part;
+ } else {
+ $joined[count($joined) - 1] .= $part;
+ }
+ }
+
+ return $joined;
+ }
+
+ // replaces all the interpolates
+ protected function evalSelector($selector) {
+ return array_map(array($this, "evalSelectorPart"), $selector);
+ }
+
+ protected function evalSelectorPart($piece) {
+ foreach ($piece as &$p) {
+ if (!is_array($p)) continue;
+
+ switch ($p[0]) {
+ case "interpolate":
+ $p = $this->compileValue($p);
+ break;
+ case "string":
+ $p = $this->compileValue($p);
+ break;
+ }
+ }
+
+ return $this->flattenSelectorSingle($piece);
+ }
+
+ // compiles to string
+ // self(&) should have been replaced by now
+ protected function compileSelector($selector) {
+ if (!is_array($selector)) return $selector; // media and the like
+
+ return implode(" ", array_map(
+ array($this, "compileSelectorPart"), $selector));
+ }
+
+ protected function compileSelectorPart($piece) {
+ foreach ($piece as &$p) {
+ if (!is_array($p)) continue;
+
+ switch ($p[0]) {
+ case "self":
+ $p = "&";
+ break;
+ default:
+ $p = $this->compileValue($p);
+ break;
+ }
+ }
+
+ return implode($piece);
+ }
+
+ protected function hasSelectorPlaceholder($selector)
+ {
+ if (!is_array($selector)) return false;
+
+ foreach ($selector as $parts) {
+ foreach ($parts as $part) {
+ if ('%' == $part[0]) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected function compileChildren($stms, $out) {
+ foreach ($stms as $stm) {
+ $ret = $this->compileChild($stm, $out);
+ if (!is_null($ret)) return $ret;
+ }
+ }
+
+ protected function compileMediaQuery($queryList) {
+ $out = "@media";
+ $first = true;
+ foreach ($queryList as $query){
+ $parts = array();
+ foreach ($query as $q) {
+ switch ($q[0]) {
+ case "mediaType":
+ $parts[] = implode(" ", array_map(array($this, "compileValue"), array_slice($q, 1)));
+ break;
+ case "mediaExp":
+ if (isset($q[2])) {
+ $parts[] = "(". $this->compileValue($q[1]) . $this->formatter->assignSeparator . $this->compileValue($q[2]) . ")";
+ } else {
+ $parts[] = "(" . $this->compileValue($q[1]) . ")";
+ }
+ break;
+ }
+ }
+ if (!empty($parts)) {
+ if ($first) {
+ $first = false;
+ $out .= " ";
+ } else {
+ $out .= $this->formatter->tagSeparator;
+ }
+ $out .= implode(" and ", $parts);
+ }
+ }
+ return $out;
+ }
+
+ // returns true if the value was something that could be imported
+ protected function compileImport($rawPath, $out) {
+ if ($rawPath[0] == "string") {
+ $path = $this->compileStringContent($rawPath);
+ if ($path = $this->findImport($path)) {
+ $this->importFile($path, $out);
+ return true;
+ }
+ return false;
+ }
+ if ($rawPath[0] == "list") {
+ // handle a list of strings
+ if (count($rawPath[2]) == 0) return false;
+ foreach ($rawPath[2] as $path) {
+ if ($path[0] != "string") return false;
+ }
+
+ foreach ($rawPath[2] as $path) {
+ $this->compileImport($path, $out);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // return a value to halt execution
+ protected function compileChild($child, $out) {
+ $this->sourcePos = isset($child[-1]) ? $child[-1] : -1;
+ $this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser;
+
+ switch ($child[0]) {
+ case "import":
+ list(,$rawPath) = $child;
+ $rawPath = $this->reduce($rawPath);
+ if (!$this->compileImport($rawPath, $out)) {
+ $out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
+ }
+ break;
+ case "directive":
+ list(, $directive) = $child;
+ $s = "@" . $directive->name;
+ if (!empty($directive->value)) {
+ $s .= " " . $this->compileValue($directive->value);
+ }
+ $this->compileNestedBlock($directive, array($s));
+ break;
+ case "media":
+ $this->compileMedia($child[1]);
+ break;
+ case "block":
+ $this->compileBlock($child[1]);
+ break;
+ case "charset":
+ $out->lines[] = "@charset ".$this->compileValue($child[1]).";";
+ break;
+ case "assign":
+ list(,$name, $value) = $child;
+ if ($name[0] == "var") {
+ $isDefault = !empty($child[3]);
+
+ if ($isDefault) {
+ $existingValue = $this->get($name[1], true);
+ $shouldSet = $existingValue === true || $existingValue == self::$null;
+ }
+
+ if (!$isDefault || $shouldSet) {
+ $this->set($name[1], $this->reduce($value));
+ }
+ break;
+ }
+
+ // if the value reduces to null from something else then
+ // the property should be discarded
+ if ($value[0] != "null") {
+ $value = $this->reduce($value);
+ if ($value[0] == "null") {
+ break;
+ }
+ }
+
+ $compiledValue = $this->compileValue($value);
+ $out->lines[] = $this->formatter->property(
+ $this->compileValue($name),
+ $compiledValue);
+ break;
+ case "comment":
+ $out->lines[] = $child[1];
+ break;
+ case "mixin":
+ case "function":
+ list(,$block) = $child;
+ $this->set(self::$namespaces[$block->type] . $block->name, $block);
+ break;
+ case "extend":
+ list(, $selectors) = $child;
+ foreach ($selectors as $sel) {
+ // only use the first one
+ $sel = current($this->evalSelector($sel));
+ $this->pushExtends($sel, $out->selectors);
+ }
+ break;
+ case "if":
+ list(, $if) = $child;
+ if ($this->isTruthy($this->reduce($if->cond, true))) {
+ return $this->compileChildren($if->children, $out);
+ } else {
+ foreach ($if->cases as $case) {
+ if ($case->type == "else" ||
+ $case->type == "elseif" && $this->isTruthy($this->reduce($case->cond)))
+ {
+ return $this->compileChildren($case->children, $out);
+ }
+ }
+ }
+ break;
+ case "return":
+ return $this->reduce($child[1], true);
+ case "each":
+ list(,$each) = $child;
+ $list = $this->coerceList($this->reduce($each->list));
+ foreach ($list[2] as $item) {
+ $this->pushEnv();
+ $this->set($each->var, $item);
+ // TODO: allow return from here
+ $this->compileChildren($each->children, $out);
+ $this->popEnv();
+ }
+ break;
+ case "while":
+ list(,$while) = $child;
+ while ($this->isTruthy($this->reduce($while->cond, true))) {
+ $ret = $this->compileChildren($while->children, $out);
+ if ($ret) return $ret;
+ }
+ break;
+ case "for":
+ list(,$for) = $child;
+ $start = $this->reduce($for->start, true);
+ $start = $start[1];
+ $end = $this->reduce($for->end, true);
+ $end = $end[1];
+ $d = $start < $end ? 1 : -1;
+
+ while (true) {
+ if ((!$for->until && $start - $d == $end) ||
+ ($for->until && $start == $end))
+ {
+ break;
+ }
+
+ $this->set($for->var, array("number", $start, ""));
+ $start += $d;
+
+ $ret = $this->compileChildren($for->children, $out);
+ if ($ret) return $ret;
+ }
+
+ break;
+ case "nestedprop":
+ list(,$prop) = $child;
+ $prefixed = array();
+ $prefix = $this->compileValue($prop->prefix) . "-";
+ foreach ($prop->children as $child) {
+ if ($child[0] == "assign") {
+ array_unshift($child[1][2], $prefix);
+ }
+ if ($child[0] == "nestedprop") {
+ array_unshift($child[1]->prefix[2], $prefix);
+ }
+ $prefixed[] = $child;
+ }
+ $this->compileChildren($prefixed, $out);
+ break;
+ case "include": // including a mixin
+ list(,$name, $argValues, $content) = $child;
+ $mixin = $this->get(self::$namespaces["mixin"] . $name, false);
+ if (!$mixin) {
+ $this->throwError("Undefined mixin $name");
+ }
+
+ $callingScope = $this->env;
+
+ // push scope, apply args
+ $this->pushEnv();
+ if ($this->env->depth > 0) {
+ $this->env->depth--;
+ }
+
+ if (!is_null($content)) {
+ $content->scope = $callingScope;
+ $this->setRaw(self::$namespaces["special"] . "content", $content);
+ }
+
+ if (!is_null($mixin->args)) {
+ $this->applyArguments($mixin->args, $argValues);
+ }
+
+ foreach ($mixin->children as $child) {
+ $this->compileChild($child, $out);
+ }
+
+ $this->popEnv();
+
+ break;
+ case "mixin_content":
+ $content = $this->get(self::$namespaces["special"] . "content");
+ if (is_null($content)) {
+ $this->throwError("Expected @content inside of mixin");
+ }
+
+ $strongTypes = array('include', 'block', 'for', 'while');
+ foreach ($content->children as $child) {
+ $this->storeEnv = (in_array($child[0], $strongTypes))
+ ? null
+ : $content->scope;
+
+ $this->compileChild($child, $out);
+ }
+
+ unset($this->storeEnv);
+ break;
+ case "debug":
+ list(,$value, $pos) = $child;
+ $line = $this->parser->getLineNo($pos);
+ $value = $this->compileValue($this->reduce($value, true));
+ fwrite(STDERR, "Line $line DEBUG: $value\n");
+ break;
+ default:
+ $this->throwError("unknown child type: $child[0]");
+ }
+ }
+
+ protected function expToString($exp) {
+ list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
+ $content = array($this->reduce($left));
+ if ($whiteLeft) $content[] = " ";
+ $content[] = $op;
+ if ($whiteRight) $content[] = " ";
+ $content[] = $this->reduce($right);
+ return array("string", "", $content);
+ }
+
+ protected function isTruthy($value) {
+ return $value != self::$false && $value != self::$null;
+ }
+
+ // should $value cause its operand to eval
+ protected function shouldEval($value) {
+ switch ($value[0]) {
+ case "exp":
+ if ($value[1] == "/") {
+ return $this->shouldEval($value[2], $value[3]);
+ }
+ case "var":
+ case "fncall":
+ return true;
+ }
+ return false;
+ }
+
+ protected function reduce($value, $inExp = false) {
+ list($type) = $value;
+ switch ($type) {
+ case "exp":
+ list(, $op, $left, $right, $inParens) = $value;
+ $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
+
+ $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
+
+ $left = $this->reduce($left, true);
+ $right = $this->reduce($right, true);
+
+ // only do division in special cases
+ if ($opName == "div" && !$inParens && !$inExp) {
+ if ($left[0] != "color" && $right[0] != "color") {
+ return $this->expToString($value);
+ }
+ }
+
+ $left = $this->coerceForExpression($left);
+ $right = $this->coerceForExpression($right);
+
+ $ltype = $left[0];
+ $rtype = $right[0];
+
+ // this tries:
+ // 1. op_[op name]_[left type]_[right type]
+ // 2. op_[left type]_[right type] (passing the op as first arg
+ // 3. op_[op name]
+ $fn = "op_${opName}_${ltype}_${rtype}";
+ if (is_callable(array($this, $fn)) ||
+ (($fn = "op_${ltype}_${rtype}") &&
+ is_callable(array($this, $fn)) &&
+ $passOp = true) ||
+ (($fn = "op_${opName}") &&
+ is_callable(array($this, $fn)) &&
+ $genOp = true))
+ {
+ $unitChange = false;
+ if (!isset($genOp) &&
+ $left[0] == "number" && $right[0] == "number")
+ {
+ if ($opName == "mod" && $right[2] != "") {
+ $this->throwError("Cannot modulo by a number with units: $right[1]$right[2].");
+ }
+
+ $unitChange = true;
+ $emptyUnit = $left[2] == "" || $right[2] == "";
+ $targetUnit = "" != $left[2] ? $left[2] : $right[2];
+
+ if ($opName != "mul") {
+ $left[2] = "" != $left[2] ? $left[2] : $targetUnit;
+ $right[2] = "" != $right[2] ? $right[2] : $targetUnit;
+ }
+
+ if ($opName != "mod") {
+ $left = $this->normalizeNumber($left);
+ $right = $this->normalizeNumber($right);
+ }
+
+ if ($opName == "div" && !$emptyUnit && $left[2] == $right[2]) {
+ $targetUnit = "";
+ }
+
+ if ($opName == "mul") {
+ $left[2] = "" != $left[2] ? $left[2] : $right[2];
+ $right[2] = "" != $right[2] ? $right[2] : $left[2];
+ } elseif ($opName == "div" && $left[2] == $right[2]) {
+ $left[2] = "";
+ $right[2] = "";
+ }
+ }
+
+ $shouldEval = $inParens || $inExp;
+ if (isset($passOp)) {
+ $out = $this->$fn($op, $left, $right, $shouldEval);
+ } else {
+ $out = $this->$fn($left, $right, $shouldEval);
+ }
+
+ if (!is_null($out)) {
+ if ($unitChange && $out[0] == "number") {
+ $out = $this->coerceUnit($out, $targetUnit);
+ }
+ return $out;
+ }
+ }
+
+ return $this->expToString($value);
+ case "unary":
+ list(, $op, $exp, $inParens) = $value;
+ $inExp = $inExp || $this->shouldEval($exp);
+
+ $exp = $this->reduce($exp);
+ if ($exp[0] == "number") {
+ switch ($op) {
+ case "+":
+ return $exp;
+ case "-":
+ $exp[1] *= -1;
+ return $exp;
+ }
+ }
+
+ if ($op == "not") {
+ if ($inExp || $inParens) {
+ if ($exp == self::$false) {
+ return self::$true;
+ } else {
+ return self::$false;
+ }
+ } else {
+ $op = $op . " ";
+ }
+ }
+
+ return array("string", "", array($op, $exp));
+ case "var":
+ list(, $name) = $value;
+ return $this->reduce($this->get($name));
+ case "list":
+ foreach ($value[2] as &$item) {
+ $item = $this->reduce($item);
+ }
+ return $value;
+ case "string":
+ foreach ($value[2] as &$item) {
+ if (is_array($item)) {
+ $item = $this->reduce($item);
+ }
+ }
+ return $value;
+ case "interpolate":
+ $value[1] = $this->reduce($value[1]);
+ return $value;
+ case "fncall":
+ list(,$name, $argValues) = $value;
+
+ // user defined function?
+ $func = $this->get(self::$namespaces["function"] . $name, false);
+ if ($func) {
+ $this->pushEnv();
+
+ // set the args
+ if (isset($func->args)) {
+ $this->applyArguments($func->args, $argValues);
+ }
+
+ // throw away lines and children
+ $tmp = (object)array(
+ "lines" => array(),
+ "children" => array()
+ );
+ $ret = $this->compileChildren($func->children, $tmp);
+ $this->popEnv();
+
+ return is_null($ret) ? self::$defaultValue : $ret;
+ }
+
+ // built in function
+ if ($this->callBuiltin($name, $argValues, $returnValue)) {
+ return $returnValue;
+ }
+
+ // need to flatten the arguments into a list
+ $listArgs = array();
+ foreach ((array)$argValues as $arg) {
+ if (empty($arg[0])) {
+ $listArgs[] = $this->reduce($arg[1]);
+ }
+ }
+ return array("function", $name, array("list", ",", $listArgs));
+ default:
+ return $value;
+ }
+ }
+
+ public function normalizeValue($value) {
+ $value = $this->coerceForExpression($this->reduce($value));
+ list($type) = $value;
+
+ switch ($type) {
+ case "list":
+ $value = $this->extractInterpolation($value);
+ if ($value[0] != "list") {
+ return array("keyword", $this->compileValue($value));
+ }
+ foreach ($value[2] as $key => $item) {
+ $value[2][$key] = $this->normalizeValue($item);
+ }
+ return $value;
+ case "number":
+ return $this->normalizeNumber($value);
+ default:
+ return $value;
+ }
+ }
+
+ // just does physical lengths for now
+ protected function normalizeNumber($number) {
+ list(, $value, $unit) = $number;
+ if (isset(self::$unitTable["in"][$unit])) {
+ $conv = self::$unitTable["in"][$unit];
+ return array("number", $value / $conv, "in");
+ }
+ return $number;
+ }
+
+ // $number should be normalized
+ protected function coerceUnit($number, $unit) {
+ list(, $value, $baseUnit) = $number;
+ if (isset(self::$unitTable[$baseUnit][$unit])) {
+ $value = $value * self::$unitTable[$baseUnit][$unit];
+ }
+
+ return array("number", $value, $unit);
+ }
+
+ protected function op_add_number_number($left, $right) {
+ return array("number", $left[1] + $right[1], $left[2]);
+ }
+
+ protected function op_mul_number_number($left, $right) {
+ return array("number", $left[1] * $right[1], $left[2]);
+ }
+
+ protected function op_sub_number_number($left, $right) {
+ return array("number", $left[1] - $right[1], $left[2]);
+ }
+
+ protected function op_div_number_number($left, $right) {
+ return array("number", $left[1] / $right[1], $left[2]);
+ }
+
+ protected function op_mod_number_number($left, $right) {
+ return array("number", $left[1] % $right[1], $left[2]);
+ }
+
+ // adding strings
+ protected function op_add($left, $right) {
+ if ($strLeft = $this->coerceString($left)) {
+ if ($right[0] == "string") {
+ $right[1] = "";
+ }
+ $strLeft[2][] = $right;
+ return $strLeft;
+ }
+
+ if ($strRight = $this->coerceString($right)) {
+ if ($left[0] == "string") {
+ $left[1] = "";
+ }
+ array_unshift($strRight[2], $left);
+ return $strRight;
+ }
+ }
+
+ protected function op_and($left, $right, $shouldEval) {
+ if (!$shouldEval) return;
+ if ($left != self::$false) return $right;
+ return $left;
+ }
+
+ protected function op_or($left, $right, $shouldEval) {
+ if (!$shouldEval) return;
+ if ($left != self::$false) return $left;
+ return $right;
+ }
+
+ protected function op_color_color($op, $left, $right) {
+ $out = array('color');
+ foreach (range(1, 3) as $i) {
+ $lval = isset($left[$i]) ? $left[$i] : 0;
+ $rval = isset($right[$i]) ? $right[$i] : 0;
+ switch ($op) {
+ case '+':
+ $out[] = $lval + $rval;
+ break;
+ case '-':
+ $out[] = $lval - $rval;
+ break;
+ case '*':
+ $out[] = $lval * $rval;
+ break;
+ case '%':
+ $out[] = $lval % $rval;
+ break;
+ case '/':
+ if ($rval == 0) {
+ $this->throwError("color: Can't divide by zero");
+ }
+ $out[] = $lval / $rval;
+ break;
+ case "==":
+ return $this->op_eq($left, $right);
+ case "!=":
+ return $this->op_neq($left, $right);
+ default:
+ $this->throwError("color: unknown op $op");
+ }
+ }
+
+ if (isset($left[4])) $out[4] = $left[4];
+ elseif (isset($right[4])) $out[4] = $right[4];
+
+ return $this->fixColor($out);
+ }
+
+ protected function op_color_number($op, $left, $right) {
+ $value = $right[1];
+ return $this->op_color_color($op, $left,
+ array("color", $value, $value, $value));
+ }
+
+ protected function op_number_color($op, $left, $right) {
+ $value = $left[1];
+ return $this->op_color_color($op,
+ array("color", $value, $value, $value), $right);
+ }
+
+ protected function op_eq($left, $right) {
+ if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
+ $lStr[1] = "";
+ $rStr[1] = "";
+ return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr));
+ }
+
+ return $this->toBool($left == $right);
+ }
+
+ protected function op_neq($left, $right) {
+ return $this->toBool($left != $right);
+ }
+
+ protected function op_gte_number_number($left, $right) {
+ return $this->toBool($left[1] >= $right[1]);
+ }
+
+ protected function op_gt_number_number($left, $right) {
+ return $this->toBool($left[1] > $right[1]);
+ }
+
+ protected function op_lte_number_number($left, $right) {
+ return $this->toBool($left[1] <= $right[1]);
+ }
+
+ protected function op_lt_number_number($left, $right) {
+ return $this->toBool($left[1] < $right[1]);
+ }
+
+ public function toBool($thing) {
+ return $thing ? self::$true : self::$false;
+ }
+
+ /**
+ * Compiles a primitive value into a CSS property value.
+ *
+ * Values in scssphp are typed by being wrapped in arrays, their format is
+ * typically:
+ *
+ * array(type, contents [, additional_contents]*)
+ *
+ * The input is expected to be reduced. This function will not work on
+ * things like expressions and variables.
+ *
+ * @param array $value
+ */
+ protected function compileValue($value) {
+ $value = $this->reduce($value);
+
+ list($type) = $value;
+ switch ($type) {
+ case "keyword":
+ return $value[1];
+ case "color":
+ // [1] - red component (either number for a %)
+ // [2] - green component
+ // [3] - blue component
+ // [4] - optional alpha component
+ list(, $r, $g, $b) = $value;
+
+ $r = round($r);
+ $g = round($g);
+ $b = round($b);
+
+ if (count($value) == 5 && $value[4] != 1) { // rgba
+ return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')';
+ }
+
+ $h = sprintf("#%02x%02x%02x", $r, $g, $b);
+
+ // Converting hex color to short notation (e.g. #003399 to #039)
+ if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
+ $h = '#' . $h[1] . $h[3] . $h[5];
+ }
+
+ return $h;
+ case "number":
+ return round($value[1], $this->numberPrecision) . $value[2];
+ case "string":
+ return $value[1] . $this->compileStringContent($value) . $value[1];
+ case "function":
+ $args = !empty($value[2]) ? $this->compileValue($value[2]) : "";
+ return "$value[1]($args)";
+ case "list":
+ $value = $this->extractInterpolation($value);
+ if ($value[0] != "list") return $this->compileValue($value);
+
+ list(, $delim, $items) = $value;
+
+ $filtered = array();
+ foreach ($items as $item) {
+ if ($item[0] == "null") continue;
+ $filtered[] = $this->compileValue($item);
+ }
+
+ return implode("$delim ", $filtered);
+ case "interpolated": # node created by extractInterpolation
+ list(, $interpolate, $left, $right) = $value;
+ list(,, $whiteLeft, $whiteRight) = $interpolate;
+
+ $left = count($left[2]) > 0 ?
+ $this->compileValue($left).$whiteLeft : "";
+
+ $right = count($right[2]) > 0 ?
+ $whiteRight.$this->compileValue($right) : "";
+
+ return $left.$this->compileValue($interpolate).$right;
+
+ case "interpolate": # raw parse node
+ list(, $exp) = $value;
+
+ // strip quotes if it's a string
+ $reduced = $this->reduce($exp);
+ switch ($reduced[0]) {
+ case "string":
+ $reduced = array("keyword",
+ $this->compileStringContent($reduced));
+ break;
+ case "null":
+ $reduced = array("keyword", "");
+ }
+
+ return $this->compileValue($reduced);
+ case "null":
+ return "null";
+ default:
+ $this->throwError("unknown value type: $type");
+ }
+ }
+
+ protected function compileStringContent($string) {
+ $parts = array();
+ foreach ($string[2] as $part) {
+ if (is_array($part)) {
+ $parts[] = $this->compileValue($part);
+ } else {
+ $parts[] = $part;
+ }
+ }
+
+ return implode($parts);
+ }
+
+ // doesn't need to be recursive, compileValue will handle that
+ protected function extractInterpolation($list) {
+ $items = $list[2];
+ foreach ($items as $i => $item) {
+ if ($item[0] == "interpolate") {
+ $before = array("list", $list[1], array_slice($items, 0, $i));
+ $after = array("list", $list[1], array_slice($items, $i + 1));
+ return array("interpolated", $item, $before, $after);
+ }
+ }
+ return $list;
+ }
+
+ // find the final set of selectors
+ protected function multiplySelectors($env) {
+ $envs = array();
+ while (null !== $env) {
+ if (!empty($env->selectors)) {
+ $envs[] = $env;
+ }
+ $env = $env->parent;
+ };
+
+ $selectors = array();
+ $parentSelectors = array(array());
+ while ($env = array_pop($envs)) {
+ $selectors = array();
+ foreach ($env->selectors as $selector) {
+ foreach ($parentSelectors as $parent) {
+ $selectors[] = $this->joinSelectors($parent, $selector);
+ }
+ }
+ $parentSelectors = $selectors;
+ }
+
+ return $selectors;
+ }
+
+ // looks for & to replace, or append parent before child
+ protected function joinSelectors($parent, $child) {
+ $setSelf = false;
+ $out = array();
+ foreach ($child as $part) {
+ $newPart = array();
+ foreach ($part as $p) {
+ if ($p == self::$selfSelector) {
+ $setSelf = true;
+ foreach ($parent as $i => $parentPart) {
+ if ($i > 0) {
+ $out[] = $newPart;
+ $newPart = array();
+ }
+
+ foreach ($parentPart as $pp) {
+ $newPart[] = $pp;
+ }
+ }
+ } else {
+ $newPart[] = $p;
+ }
+ }
+
+ $out[] = $newPart;
+ }
+
+ return $setSelf ? $out : array_merge($parent, $child);
+ }
+
+ protected function multiplyMedia($env, $childQueries = null) {
+ if (is_null($env) ||
+ !empty($env->block->type) && $env->block->type != "media")
+ {
+ return $childQueries;
+ }
+
+ // plain old block, skip
+ if (empty($env->block->type)) {
+ return $this->multiplyMedia($env->parent, $childQueries);
+ }
+
+ $parentQueries = $env->block->queryList;
+ if ($childQueries == null) {
+ $childQueries = $parentQueries;
+ } else {
+ $originalQueries = $childQueries;
+ $childQueries = array();
+
+ foreach ($parentQueries as $parentQuery){
+ foreach ($originalQueries as $childQuery) {
+ $childQueries []= array_merge($parentQuery, $childQuery);
+ }
+ }
+ }
+
+ return $this->multiplyMedia($env->parent, $childQueries);
+ }
+
+ // convert something to list
+ protected function coerceList($item, $delim = ",") {
+ if (!is_null($item) && $item[0] == "list") {
+ return $item;
+ }
+
+ return array("list", $delim, is_null($item) ? array(): array($item));
+ }
+
+ protected function applyArguments($argDef, $argValues) {
+ $hasVariable = false;
+ $args = array();
+ foreach ($argDef as $i => $arg) {
+ list($name, $default, $isVariable) = $argDef[$i];
+ $args[$name] = array($i, $name, $default, $isVariable);
+ $hasVariable |= $isVariable;
+ }
+
+ $keywordArgs = array();
+ $deferredKeywordArgs = array();
+ $remaining = array();
+ // assign the keyword args
+ foreach ((array) $argValues as $arg) {
+ if (!empty($arg[0])) {
+ if (!isset($args[$arg[0][1]])) {
+ if ($hasVariable) {
+ $deferredKeywordArgs[$arg[0][1]] = $arg[1];
+ } else {
+ $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
+ }
+ } elseif ($args[$arg[0][1]][0] < count($remaining)) {
+ $this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
+ } else {
+ $keywordArgs[$arg[0][1]] = $arg[1];
+ }
+ } elseif (count($keywordArgs)) {
+ $this->throwError('Positional arguments must come before keyword arguments.');
+ } elseif ($arg[2] == true) {
+ $val = $this->reduce($arg[1], true);
+ if ($val[0] == "list") {
+ foreach ($val[2] as $name => $item) {
+ if (!is_numeric($name)) {
+ $keywordArgs[$name] = $item;
+ } else {
+ $remaining[] = $item;
+ }
+ }
+ } else {
+ $remaining[] = $val;
+ }
+ } else {
+ $remaining[] = $arg[1];
+ }
+ }
+
+ foreach ($args as $arg) {
+ list($i, $name, $default, $isVariable) = $arg;
+ if ($isVariable) {
+ $val = array("list", ",", array());
+ for ($count = count($remaining); $i < $count; $i++) {
+ $val[2][] = $remaining[$i];
+ }
+ foreach ($deferredKeywordArgs as $itemName => $item) {
+ $val[2][$itemName] = $item;
+ }
+ } elseif (isset($remaining[$i])) {
+ $val = $remaining[$i];
+ } elseif (isset($keywordArgs[$name])) {
+ $val = $keywordArgs[$name];
+ } elseif (!empty($default)) {
+ $val = $default;
+ } else {
+ $this->throwError("Missing argument $name");
+ }
+
+ $this->set($name, $this->reduce($val, true), true);
+ }
+ }
+
+ protected function pushEnv($block=null) {
+ $env = new stdClass;
+ $env->parent = $this->env;
+ $env->store = array();
+ $env->block = $block;
+ $env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0;
+
+ $this->env = $env;
+ return $env;
+ }
+
+ protected function normalizeName($name) {
+ return str_replace("-", "_", $name);
+ }
+
+ protected function getStoreEnv() {
+ return isset($this->storeEnv) ? $this->storeEnv : $this->env;
+ }
+
+ protected function set($name, $value, $shadow=false) {
+ $name = $this->normalizeName($name);
+
+ if ($shadow) {
+ $this->setRaw($name, $value);
+ } else {
+ $this->setExisting($name, $value);
+ }
+ }
+
+ protected function setExisting($name, $value, $env = null) {
+ if (is_null($env)) $env = $this->getStoreEnv();
+
+ if (isset($env->store[$name]) || is_null($env->parent)) {
+ $env->store[$name] = $value;
+ } else {
+ $this->setExisting($name, $value, $env->parent);
+ }
+ }
+
+ protected function setRaw($name, $value) {
+ $env = $this->getStoreEnv();
+ $env->store[$name] = $value;
+ }
+
+ public function get($name, $defaultValue = null, $env = null) {
+ $name = $this->normalizeName($name);
+
+ if (is_null($env)) $env = $this->getStoreEnv();
+ if (is_null($defaultValue)) $defaultValue = self::$defaultValue;
+
+ if (isset($env->store[$name])) {
+ return $env->store[$name];
+ } elseif (isset($env->parent)) {
+ return $this->get($name, $defaultValue, $env->parent);
+ }
+
+ return $defaultValue; // found nothing
+ }
+
+ protected function popEnv() {
+ $env = $this->env;
+ $this->env = $this->env->parent;
+ return $env;
+ }
+
+ public function getParsedFiles() {
+ return $this->parsedFiles;
+ }
+
+ public function addImportPath($path) {
+ $this->importPaths[] = $path;
+ }
+
+ public function setImportPaths($path) {
+ $this->importPaths = (array)$path;
+ }
+
+ public function setNumberPrecision($numberPrecision) {
+ $this->numberPrecision = $numberPrecision;
+ }
+
+ public function setFormatter($formatterName) {
+ $this->formatter = $formatterName;
+ }
+
+ public function registerFunction($name, $func) {
+ $this->userFunctions[$this->normalizeName($name)] = $func;
+ }
+
+ public function unregisterFunction($name) {
+ unset($this->userFunctions[$this->normalizeName($name)]);
+ }
+
+ protected function importFile($path, $out) {
+ // see if tree is cached
+ $realPath = realpath($path);
+ if (isset($this->importCache[$realPath])) {
+ $tree = $this->importCache[$realPath];
+ } else {
+ $code = file_get_contents($path);
+ $parser = new scss_parser($path, false);
+ $tree = $parser->parse($code);
+ $this->parsedFiles[] = $path;
+
+ $this->importCache[$realPath] = $tree;
+ }
+
+ $pi = pathinfo($path);
+ array_unshift($this->importPaths, $pi['dirname']);
+ $this->compileChildren($tree->children, $out);
+ array_shift($this->importPaths);
+ }
+
+ // results the file path for an import url if it exists
+ public function findImport($url) {
+ $urls = array();
+
+ // for "normal" scss imports (ignore vanilla css and external requests)
+ if (!preg_match('/\.css|^http:\/\/$/', $url)) {
+ // try both normal and the _partial filename
+ $urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url));
+ }
+
+ foreach ($this->importPaths as $dir) {
+ if (is_string($dir)) {
+ // check urls for normal import paths
+ foreach ($urls as $full) {
+ $full = $dir .
+ (!empty($dir) && substr($dir, -1) != '/' ? '/' : '') .
+ $full;
+
+ if ($this->fileExists($file = $full.'.scss') ||
+ $this->fileExists($file = $full))
+ {
+ return $file;
+ }
+ }
+ } else {
+ // check custom callback for import path
+ $file = call_user_func($dir,$url,$this);
+ if ($file !== null) {
+ return $file;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected function fileExists($name) {
+ return is_file($name);
+ }
+
+ protected function callBuiltin($name, $args, &$returnValue) {
+ // try a lib function
+ $name = $this->normalizeName($name);
+ $libName = "lib_".$name;
+ $f = array($this, $libName);
+ $prototype = isset(self::$$libName) ? self::$$libName : null;
+
+ if (is_callable($f)) {
+ $sorted = $this->sortArgs($prototype, $args);
+ foreach ($sorted as &$val) {
+ $val = $this->reduce($val, true);
+ }
+ $returnValue = call_user_func($f, $sorted, $this);
+ } elseif (isset($this->userFunctions[$name])) {
+ // see if we can find a user function
+ $fn = $this->userFunctions[$name];
+
+ foreach ($args as &$val) {
+ $val = $this->reduce($val[1], true);
+ }
+
+ $returnValue = call_user_func($fn, $args, $this);
+ }
+
+ if (isset($returnValue)) {
+ // coerce a php value into a scss one
+ if (is_numeric($returnValue)) {
+ $returnValue = array('number', $returnValue, "");
+ } elseif (is_bool($returnValue)) {
+ $returnValue = $returnValue ? self::$true : self::$false;
+ } elseif (!is_array($returnValue)) {
+ $returnValue = array('keyword', $returnValue);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // sorts any keyword arguments
+ // TODO: merge with apply arguments
+ protected function sortArgs($prototype, $args) {
+ $keyArgs = array();
+ $posArgs = array();
+
+ foreach ($args as $arg) {
+ list($key, $value) = $arg;
+ $key = $key[1];
+ if (empty($key)) {
+ $posArgs[] = $value;
+ } else {
+ $keyArgs[$key] = $value;
+ }
+ }
+
+ if (is_null($prototype)) return $posArgs;
+
+ $finalArgs = array();
+ foreach ($prototype as $i => $names) {
+ if (isset($posArgs[$i])) {
+ $finalArgs[] = $posArgs[$i];
+ continue;
+ }
+
+ $set = false;
+ foreach ((array)$names as $name) {
+ if (isset($keyArgs[$name])) {
+ $finalArgs[] = $keyArgs[$name];
+ $set = true;
+ break;
+ }
+ }
+
+ if (!$set) {
+ $finalArgs[] = null;
+ }
+ }
+
+ return $finalArgs;
+ }
+
+ protected function coerceForExpression($value) {
+ if ($color = $this->coerceColor($value)) {
+ return $color;
+ }
+
+ return $value;
+ }
+
+ protected function coerceColor($value) {
+ switch ($value[0]) {
+ case "color": return $value;
+ case "keyword":
+ $name = $value[1];
+ if (isset(self::$cssColors[$name])) {
+ @list($r, $g, $b, $a) = explode(',', self::$cssColors[$name]);
+ return isset($a)
+ ? array('color', (int) $r, (int) $g, (int) $b, (int) $a)
+ : array('color', (int) $r, (int) $g, (int) $b);
+ }
+ return null;
+ }
+
+ return null;
+ }
+
+ protected function coerceString($value) {
+ switch ($value[0]) {
+ case "string":
+ return $value;
+ case "keyword":
+ return array("string", "", array($value[1]));
+ }
+ return null;
+ }
+
+ public function assertList($value) {
+ if ($value[0] != "list")
+ $this->throwError("expecting list");
+ return $value;
+ }
+
+ public function assertColor($value) {
+ if ($color = $this->coerceColor($value)) return $color;
+ $this->throwError("expecting color");
+ }
+
+ public function assertNumber($value) {
+ if ($value[0] != "number")
+ $this->throwError("expecting number");
+ return $value[1];
+ }
+
+ protected function coercePercent($value) {
+ if ($value[0] == "number") {
+ if ($value[2] == "%") {
+ return $value[1] / 100;
+ }
+ return $value[1];
+ }
+ return 0;
+ }
+
+ // make sure a color's components don't go out of bounds
+ protected function fixColor($c) {
+ foreach (range(1, 3) as $i) {
+ if ($c[$i] < 0) $c[$i] = 0;
+ if ($c[$i] > 255) $c[$i] = 255;
+ }
+
+ return $c;
+ }
+
+ public function toHSL($red, $green, $blue) {
+ $r = $red / 255;
+ $g = $green / 255;
+ $b = $blue / 255;
+
+ $min = min($r, $g, $b);
+ $max = max($r, $g, $b);
+ $d = $max - $min;
+ $l = ($min + $max) / 2;
+
+ if ($min == $max) {
+ $s = $h = 0;
+ } else {
+ if ($l < 0.5)
+ $s = $d / (2 * $l);
+ else
+ $s = $d / (2 - 2 * $l);
+
+ if ($r == $max)
+ $h = 60 * ($g - $b) / $d;
+ elseif ($g == $max)
+ $h = 60 * ($b - $r) / $d + 120;
+ elseif ($b == $max)
+ $h = 60 * ($r - $g) / $d + 240;
+ }
+
+ return array('hsl', fmod($h, 360), $s * 100, $l * 100);
+ }
+
+ public function hueToRGB($m1, $m2, $h) {
+ if ($h < 0)
+ $h += 1;
+ elseif ($h > 1)
+ $h -= 1;
+
+ if ($h * 6 < 1)
+ return $m1 + ($m2 - $m1) * $h * 6;
+
+ if ($h * 2 < 1)
+ return $m2;
+
+ if ($h * 3 < 2)
+ return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
+
+ return $m1;
+ }
+
+ // H from 0 to 360, S and L from 0 to 100
+ public function toRGB($hue, $saturation, $lightness) {
+ if ($hue < 0) {
+ $hue += 360;
+ }
+
+ $h = $hue / 360;
+ $s = min(100, max(0, $saturation)) / 100;
+ $l = min(100, max(0, $lightness)) / 100;
+
+ $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
+ $m1 = $l * 2 - $m2;
+
+ $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
+ $g = $this->hueToRGB($m1, $m2, $h) * 255;
+ $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
+
+ $out = array('color', $r, $g, $b);
+ return $out;
+ }
+
+ // Built in functions
+
+ protected static $lib_if = array("condition", "if-true", "if-false");
+ protected function lib_if($args) {
+ list($cond,$t, $f) = $args;
+ if ($cond == self::$false) return $f;
+ return $t;
+ }
+
+ protected static $lib_index = array("list", "value");
+ protected function lib_index($args) {
+ list($list, $value) = $args;
+ $list = $this->assertList($list);
+
+ $values = array();
+ foreach ($list[2] as $item) {
+ $values[] = $this->normalizeValue($item);
+ }
+ $key = array_search($this->normalizeValue($value), $values);
+
+ return false === $key ? false : $key + 1;
+ }
+
+ protected static $lib_rgb = array("red", "green", "blue");
+ protected function lib_rgb($args) {
+ list($r,$g,$b) = $args;
+ return array("color", $r[1], $g[1], $b[1]);
+ }
+
+ protected static $lib_rgba = array(
+ array("red", "color"),
+ "green", "blue", "alpha");
+ protected function lib_rgba($args) {
+ if ($color = $this->coerceColor($args[0])) {
+ $num = is_null($args[1]) ? $args[3] : $args[1];
+ $alpha = $this->assertNumber($num);
+ $color[4] = $alpha;
+ return $color;
+ }
+
+ list($r,$g,$b, $a) = $args;
+ return array("color", $r[1], $g[1], $b[1], $a[1]);
+ }
+
+ // helper function for adjust_color, change_color, and scale_color
+ protected function alter_color($args, $fn) {
+ $color = $this->assertColor($args[0]);
+
+ foreach (array(1,2,3,7) as $i) {
+ if (!is_null($args[$i])) {
+ $val = $this->assertNumber($args[$i]);
+ $ii = $i == 7 ? 4 : $i; // alpha
+ $color[$ii] =
+ $this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
+ }
+ }
+
+ if (!is_null($args[4]) || !is_null($args[5]) || !is_null($args[6])) {
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
+ foreach (array(4,5,6) as $i) {
+ if (!is_null($args[$i])) {
+ $val = $this->assertNumber($args[$i]);
+ $hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
+ }
+ }
+
+ $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
+ if (isset($color[4])) $rgb[4] = $color[4];
+ $color = $rgb;
+ }
+
+ return $color;
+ }
+
+ protected static $lib_adjust_color = array(
+ "color", "red", "green", "blue",
+ "hue", "saturation", "lightness", "alpha"
+ );
+ protected function adjust_color_helper($base, $alter, $i) {
+ return $base += $alter;
+ }
+ protected function lib_adjust_color($args) {
+ return $this->alter_color($args, "adjust_color_helper");
+ }
+
+ protected static $lib_change_color = array(
+ "color", "red", "green", "blue",
+ "hue", "saturation", "lightness", "alpha"
+ );
+ protected function change_color_helper($base, $alter, $i) {
+ return $alter;
+ }
+ protected function lib_change_color($args) {
+ return $this->alter_color($args, "change_color_helper");
+ }
+
+ protected static $lib_scale_color = array(
+ "color", "red", "green", "blue",
+ "hue", "saturation", "lightness", "alpha"
+ );
+ protected function scale_color_helper($base, $scale, $i) {
+ // 1,2,3 - rgb
+ // 4, 5, 6 - hsl
+ // 7 - a
+ switch ($i) {
+ case 1:
+ case 2:
+ case 3:
+ $max = 255; break;
+ case 4:
+ $max = 360; break;
+ case 7:
+ $max = 1; break;
+ default:
+ $max = 100;
+ }
+
+ $scale = $scale / 100;
+ if ($scale < 0) {
+ return $base * $scale + $base;
+ } else {
+ return ($max - $base) * $scale + $base;
+ }
+ }
+ protected function lib_scale_color($args) {
+ return $this->alter_color($args, "scale_color_helper");
+ }
+
+ protected static $lib_ie_hex_str = array("color");
+ protected function lib_ie_hex_str($args) {
+ $color = $this->coerceColor($args[0]);
+ $color[4] = isset($color[4]) ? round(255*$color[4]) : 255;
+
+ return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
+ }
+
+ protected static $lib_red = array("color");
+ protected function lib_red($args) {
+ $color = $this->coerceColor($args[0]);
+ return $color[1];
+ }
+
+ protected static $lib_green = array("color");
+ protected function lib_green($args) {
+ $color = $this->coerceColor($args[0]);
+ return $color[2];
+ }
+
+ protected static $lib_blue = array("color");
+ protected function lib_blue($args) {
+ $color = $this->coerceColor($args[0]);
+ return $color[3];
+ }
+
+ protected static $lib_alpha = array("color");
+ protected function lib_alpha($args) {
+ if ($color = $this->coerceColor($args[0])) {
+ return isset($color[4]) ? $color[4] : 1;
+ }
+
+ // this might be the IE function, so return value unchanged
+ return null;
+ }
+
+ protected static $lib_opacity = array("color");
+ protected function lib_opacity($args) {
+ $value = $args[0];
+ if ($value[0] === 'number') return null;
+ return $this->lib_alpha($args);
+ }
+
+ // mix two colors
+ protected static $lib_mix = array("color-1", "color-2", "weight");
+ protected function lib_mix($args) {
+ list($first, $second, $weight) = $args;
+ $first = $this->assertColor($first);
+ $second = $this->assertColor($second);
+
+ if (is_null($weight)) {
+ $weight = 0.5;
+ } else {
+ $weight = $this->coercePercent($weight);
+ }
+
+ $firstAlpha = isset($first[4]) ? $first[4] : 1;
+ $secondAlpha = isset($second[4]) ? $second[4] : 1;
+
+ $w = $weight * 2 - 1;
+ $a = $firstAlpha - $secondAlpha;
+
+ $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
+ $w2 = 1.0 - $w1;
+
+ $new = array('color',
+ $w1 * $first[1] + $w2 * $second[1],
+ $w1 * $first[2] + $w2 * $second[2],
+ $w1 * $first[3] + $w2 * $second[3],
+ );
+
+ if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
+ $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
+ }
+
+ return $this->fixColor($new);
+ }
+
+ protected static $lib_hsl = array("hue", "saturation", "lightness");
+ protected function lib_hsl($args) {
+ list($h, $s, $l) = $args;
+ return $this->toRGB($h[1], $s[1], $l[1]);
+ }
+
+ protected static $lib_hsla = array("hue", "saturation",
+ "lightness", "alpha");
+ protected function lib_hsla($args) {
+ list($h, $s, $l, $a) = $args;
+ $color = $this->toRGB($h[1], $s[1], $l[1]);
+ $color[4] = $a[1];
+ return $color;
+ }
+
+ protected static $lib_hue = array("color");
+ protected function lib_hue($args) {
+ $color = $this->assertColor($args[0]);
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
+ return array("number", $hsl[1], "deg");
+ }
+
+ protected static $lib_saturation = array("color");
+ protected function lib_saturation($args) {
+ $color = $this->assertColor($args[0]);
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
+ return array("number", $hsl[2], "%");
+ }
+
+ protected static $lib_lightness = array("color");
+ protected function lib_lightness($args) {
+ $color = $this->assertColor($args[0]);
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
+ return array("number", $hsl[3], "%");
+ }
+
+ protected function adjustHsl($color, $idx, $amount) {
+ $hsl = $this->toHSL($color[1], $color[2], $color[3]);
+ $hsl[$idx] += $amount;
+ $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
+ if (isset($color[4])) $out[4] = $color[4];
+ return $out;
+ }
+
+ protected static $lib_adjust_hue = array("color", "degrees");
+ protected function lib_adjust_hue($args) {
+ $color = $this->assertColor($args[0]);
+ $degrees = $this->assertNumber($args[1]);
+ return $this->adjustHsl($color, 1, $degrees);
+ }
+
+ protected static $lib_lighten = array("color", "amount");
+ protected function lib_lighten($args) {
+ $color = $this->assertColor($args[0]);
+ $amount = 100*$this->coercePercent($args[1]);
+ return $this->adjustHsl($color, 3, $amount);
+ }
+
+ protected static $lib_darken = array("color", "amount");
+ protected function lib_darken($args) {
+ $color = $this->assertColor($args[0]);
+ $amount = 100*$this->coercePercent($args[1]);
+ return $this->adjustHsl($color, 3, -$amount);
+ }
+
+ protected static $lib_saturate = array("color", "amount");
+ protected function lib_saturate($args) {
+ $value = $args[0];
+ if ($value[0] === 'number') return null;
+ $color = $this->assertColor($value);
+ $amount = 100*$this->coercePercent($args[1]);
+ return $this->adjustHsl($color, 2, $amount);
+ }
+
+ protected static $lib_desaturate = array("color", "amount");
+ protected function lib_desaturate($args) {
+ $color = $this->assertColor($args[0]);
+ $amount = 100*$this->coercePercent($args[1]);
+ return $this->adjustHsl($color, 2, -$amount);
+ }
+
+ protected static $lib_grayscale = array("color");
+ protected function lib_grayscale($args) {
+ $value = $args[0];
+ if ($value[0] === 'number') return null;
+ return $this->adjustHsl($this->assertColor($value), 2, -100);
+ }
+
+ protected static $lib_complement = array("color");
+ protected function lib_complement($args) {
+ return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
+ }
+
+ protected static $lib_invert = array("color");
+ protected function lib_invert($args) {
+ $value = $args[0];
+ if ($value[0] === 'number') return null;
+ $color = $this->assertColor($value);
+ $color[1] = 255 - $color[1];
+ $color[2] = 255 - $color[2];
+ $color[3] = 255 - $color[3];
+ return $color;
+ }
+
+ // increases opacity by amount
+ protected static $lib_opacify = array("color", "amount");
+ protected function lib_opacify($args) {
+ $color = $this->assertColor($args[0]);
+ $amount = $this->coercePercent($args[1]);
+
+ $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
+ $color[4] = min(1, max(0, $color[4]));
+ return $color;
+ }
+
+ protected static $lib_fade_in = array("color", "amount");
+ protected function lib_fade_in($args) {
+ return $this->lib_opacify($args);
+ }
+
+ // decreases opacity by amount
+ protected static $lib_transparentize = array("color", "amount");
+ protected function lib_transparentize($args) {
+ $color = $this->assertColor($args[0]);
+ $amount = $this->coercePercent($args[1]);
+
+ $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
+ $color[4] = min(1, max(0, $color[4]));
+ return $color;
+ }
+
+ protected static $lib_fade_out = array("color", "amount");
+ protected function lib_fade_out($args) {
+ return $this->lib_transparentize($args);
+ }
+
+ protected static $lib_unquote = array("string");
+ protected function lib_unquote($args) {
+ $str = $args[0];
+ if ($str[0] == "string") $str[1] = "";
+ return $str;
+ }
+
+ protected static $lib_quote = array("string");
+ protected function lib_quote($args) {
+ $value = $args[0];
+ if ($value[0] == "string" && !empty($value[1]))
+ return $value;
+ return array("string", '"', array($value));
+ }
+
+ protected static $lib_percentage = array("value");
+ protected function lib_percentage($args) {
+ return array("number",
+ $this->coercePercent($args[0]) * 100,
+ "%");
+ }
+
+ protected static $lib_round = array("value");
+ protected function lib_round($args) {
+ $num = $args[0];
+ $num[1] = round($num[1]);
+ return $num;
+ }
+
+ protected static $lib_floor = array("value");
+ protected function lib_floor($args) {
+ $num = $args[0];
+ $num[1] = floor($num[1]);
+ return $num;
+ }
+
+ protected static $lib_ceil = array("value");
+ protected function lib_ceil($args) {
+ $num = $args[0];
+ $num[1] = ceil($num[1]);
+ return $num;
+ }
+
+ protected static $lib_abs = array("value");
+ protected function lib_abs($args) {
+ $num = $args[0];
+ $num[1] = abs($num[1]);
+ return $num;
+ }
+
+ protected function lib_min($args) {
+ $numbers = $this->getNormalizedNumbers($args);
+ $min = null;
+ foreach ($numbers as $key => $number) {
+ if (null === $min || $number[1] <= $min[1]) {
+ $min = array($key, $number[1]);
+ }
+ }
+
+ return $args[$min[0]];
+ }
+
+ protected function lib_max($args) {
+ $numbers = $this->getNormalizedNumbers($args);
+ $max = null;
+ foreach ($numbers as $key => $number) {
+ if (null === $max || $number[1] >= $max[1]) {
+ $max = array($key, $number[1]);
+ }
+ }
+
+ return $args[$max[0]];
+ }
+
+ protected function getNormalizedNumbers($args) {
+ $unit = null;
+ $originalUnit = null;
+ $numbers = array();
+ foreach ($args as $key => $item) {
+ if ('number' != $item[0]) {
+ $this->throwError("%s is not a number", $item[0]);
+ }
+ $number = $this->normalizeNumber($item);
+
+ if (null === $unit) {
+ $unit = $number[2];
+ $originalUnit = $item[2];
+ } elseif ($unit !== $number[2]) {
+ $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]);
+ }
+
+ $numbers[$key] = $number;
+ }
+
+ return $numbers;
+ }
+
+ protected static $lib_length = array("list");
+ protected function lib_length($args) {
+ $list = $this->coerceList($args[0]);
+ return count($list[2]);
+ }
+
+ protected static $lib_nth = array("list", "n");
+ protected function lib_nth($args) {
+ $list = $this->coerceList($args[0]);
+ $n = $this->assertNumber($args[1]) - 1;
+ return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
+ }
+
+ protected function listSeparatorForJoin($list1, $sep) {
+ if (is_null($sep)) return $list1[1];
+ switch ($this->compileValue($sep)) {
+ case "comma":
+ return ",";
+ case "space":
+ return "";
+ default:
+ return $list1[1];
+ }
+ }
+
+ protected static $lib_join = array("list1", "list2", "separator");
+ protected function lib_join($args) {
+ list($list1, $list2, $sep) = $args;
+ $list1 = $this->coerceList($list1, " ");
+ $list2 = $this->coerceList($list2, " ");
+ $sep = $this->listSeparatorForJoin($list1, $sep);
+ return array("list", $sep, array_merge($list1[2], $list2[2]));
+ }
+
+ protected static $lib_append = array("list", "val", "separator");
+ protected function lib_append($args) {
+ list($list1, $value, $sep) = $args;
+ $list1 = $this->coerceList($list1, " ");
+ $sep = $this->listSeparatorForJoin($list1, $sep);
+ return array("list", $sep, array_merge($list1[2], array($value)));
+ }
+
+ protected function lib_zip($args) {
+ foreach ($args as $arg) {
+ $this->assertList($arg);
+ }
+
+ $lists = array();
+ $firstList = array_shift($args);
+ foreach ($firstList[2] as $key => $item) {
+ $list = array("list", "", array($item));
+ foreach ($args as $arg) {
+ if (isset($arg[2][$key])) {
+ $list[2][] = $arg[2][$key];
+ } else {
+ break 2;
+ }
+ }
+ $lists[] = $list;
+ }
+
+ return array("list", ",", $lists);
+ }
+
+ protected static $lib_type_of = array("value");
+ protected function lib_type_of($args) {
+ $value = $args[0];
+ switch ($value[0]) {
+ case "keyword":
+ if ($value == self::$true || $value == self::$false) {
+ return "bool";
+ }
+
+ if ($this->coerceColor($value)) {
+ return "color";
+ }
+
+ return "string";
+ default:
+ return $value[0];
+ }
+ }
+
+ protected static $lib_unit = array("number");
+ protected function lib_unit($args) {
+ $num = $args[0];
+ if ($num[0] == "number") {
+ return array("string", '"', array($num[2]));
+ }
+ return "";
+ }
+
+ protected static $lib_unitless = array("number");
+ protected function lib_unitless($args) {
+ $value = $args[0];
+ return $value[0] == "number" && empty($value[2]);
+ }
+
+ protected static $lib_comparable = array("number-1", "number-2");
+ protected function lib_comparable($args) {
+ list($number1, $number2) = $args;
+ if (!isset($number1[0]) || $number1[0] != "number" || !isset($number2[0]) || $number2[0] != "number") {
+ $this->throwError('Invalid argument(s) for "comparable"');
+ }
+
+ $number1 = $this->normalizeNumber($number1);
+ $number2 = $this->normalizeNumber($number2);
+
+ return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == "";
+ }
+
+ /**
+ * Workaround IE7's content counter bug.
+ *
+ * @param array $args
+ */
+ protected function lib_counter($args) {
+ $list = array_map(array($this, 'compileValue'), $args);
+ return array('string', '', array('counter(' . implode(',', $list) . ')'));
+ }
+
+ public function throwError($msg = null) {
+ if (func_num_args() > 1) {
+ $msg = call_user_func_array("sprintf", func_get_args());
+ }
+
+ if ($this->sourcePos >= 0 && isset($this->sourceParser)) {
+ $this->sourceParser->throwParseError($msg, $this->sourcePos);
+ }
+
+ throw new Exception($msg);
+ }
+
+ /**
+ * CSS Colors
+ *
+ * @see http://www.w3.org/TR/css3-color
+ */
+ static protected $cssColors = array(
+ 'aliceblue' => '240,248,255',
+ 'antiquewhite' => '250,235,215',
+ 'aqua' => '0,255,255',
+ 'aquamarine' => '127,255,212',
+ 'azure' => '240,255,255',
+ 'beige' => '245,245,220',
+ 'bisque' => '255,228,196',
+ 'black' => '0,0,0',
+ 'blanchedalmond' => '255,235,205',
+ 'blue' => '0,0,255',
+ 'blueviolet' => '138,43,226',
+ 'brown' => '165,42,42',
+ 'burlywood' => '222,184,135',
+ 'cadetblue' => '95,158,160',
+ 'chartreuse' => '127,255,0',
+ 'chocolate' => '210,105,30',
+ 'coral' => '255,127,80',
+ 'cornflowerblue' => '100,149,237',
+ 'cornsilk' => '255,248,220',
+ 'crimson' => '220,20,60',
+ 'cyan' => '0,255,255',
+ 'darkblue' => '0,0,139',
+ 'darkcyan' => '0,139,139',
+ 'darkgoldenrod' => '184,134,11',
+ 'darkgray' => '169,169,169',
+ 'darkgreen' => '0,100,0',
+ 'darkgrey' => '169,169,169',
+ 'darkkhaki' => '189,183,107',
+ 'darkmagenta' => '139,0,139',
+ 'darkolivegreen' => '85,107,47',
+ 'darkorange' => '255,140,0',
+ 'darkorchid' => '153,50,204',
+ 'darkred' => '139,0,0',
+ 'darksalmon' => '233,150,122',
+ 'darkseagreen' => '143,188,143',
+ 'darkslateblue' => '72,61,139',
+ 'darkslategray' => '47,79,79',
+ 'darkslategrey' => '47,79,79',
+ 'darkturquoise' => '0,206,209',
+ 'darkviolet' => '148,0,211',
+ 'deeppink' => '255,20,147',
+ 'deepskyblue' => '0,191,255',
+ 'dimgray' => '105,105,105',
+ 'dimgrey' => '105,105,105',
+ 'dodgerblue' => '30,144,255',
+ 'firebrick' => '178,34,34',
+ 'floralwhite' => '255,250,240',
+ 'forestgreen' => '34,139,34',
+ 'fuchsia' => '255,0,255',
+ 'gainsboro' => '220,220,220',
+ 'ghostwhite' => '248,248,255',
+ 'gold' => '255,215,0',
+ 'goldenrod' => '218,165,32',
+ 'gray' => '128,128,128',
+ 'green' => '0,128,0',
+ 'greenyellow' => '173,255,47',
+ 'grey' => '128,128,128',
+ 'honeydew' => '240,255,240',
+ 'hotpink' => '255,105,180',
+ 'indianred' => '205,92,92',
+ 'indigo' => '75,0,130',
+ 'ivory' => '255,255,240',
+ 'khaki' => '240,230,140',
+ 'lavender' => '230,230,250',
+ 'lavenderblush' => '255,240,245',
+ 'lawngreen' => '124,252,0',
+ 'lemonchiffon' => '255,250,205',
+ 'lightblue' => '173,216,230',
+ 'lightcoral' => '240,128,128',
+ 'lightcyan' => '224,255,255',
+ 'lightgoldenrodyellow' => '250,250,210',
+ 'lightgray' => '211,211,211',
+ 'lightgreen' => '144,238,144',
+ 'lightgrey' => '211,211,211',
+ 'lightpink' => '255,182,193',
+ 'lightsalmon' => '255,160,122',
+ 'lightseagreen' => '32,178,170',
+ 'lightskyblue' => '135,206,250',
+ 'lightslategray' => '119,136,153',
+ 'lightslategrey' => '119,136,153',
+ 'lightsteelblue' => '176,196,222',
+ 'lightyellow' => '255,255,224',
+ 'lime' => '0,255,0',
+ 'limegreen' => '50,205,50',
+ 'linen' => '250,240,230',
+ 'magenta' => '255,0,255',
+ 'maroon' => '128,0,0',
+ 'mediumaquamarine' => '102,205,170',
+ 'mediumblue' => '0,0,205',
+ 'mediumorchid' => '186,85,211',
+ 'mediumpurple' => '147,112,219',
+ 'mediumseagreen' => '60,179,113',
+ 'mediumslateblue' => '123,104,238',
+ 'mediumspringgreen' => '0,250,154',
+ 'mediumturquoise' => '72,209,204',
+ 'mediumvioletred' => '199,21,133',
+ 'midnightblue' => '25,25,112',
+ 'mintcream' => '245,255,250',
+ 'mistyrose' => '255,228,225',
+ 'moccasin' => '255,228,181',
+ 'navajowhite' => '255,222,173',
+ 'navy' => '0,0,128',
+ 'oldlace' => '253,245,230',
+ 'olive' => '128,128,0',
+ 'olivedrab' => '107,142,35',
+ 'orange' => '255,165,0',
+ 'orangered' => '255,69,0',
+ 'orchid' => '218,112,214',
+ 'palegoldenrod' => '238,232,170',
+ 'palegreen' => '152,251,152',
+ 'paleturquoise' => '175,238,238',
+ 'palevioletred' => '219,112,147',
+ 'papayawhip' => '255,239,213',
+ 'peachpuff' => '255,218,185',
+ 'peru' => '205,133,63',
+ 'pink' => '255,192,203',
+ 'plum' => '221,160,221',
+ 'powderblue' => '176,224,230',
+ 'purple' => '128,0,128',
+ 'red' => '255,0,0',
+ 'rosybrown' => '188,143,143',
+ 'royalblue' => '65,105,225',
+ 'saddlebrown' => '139,69,19',
+ 'salmon' => '250,128,114',
+ 'sandybrown' => '244,164,96',
+ 'seagreen' => '46,139,87',
+ 'seashell' => '255,245,238',
+ 'sienna' => '160,82,45',
+ 'silver' => '192,192,192',
+ 'skyblue' => '135,206,235',
+ 'slateblue' => '106,90,205',
+ 'slategray' => '112,128,144',
+ 'slategrey' => '112,128,144',
+ 'snow' => '255,250,250',
+ 'springgreen' => '0,255,127',
+ 'steelblue' => '70,130,180',
+ 'tan' => '210,180,140',
+ 'teal' => '0,128,128',
+ 'thistle' => '216,191,216',
+ 'tomato' => '255,99,71',
+ 'transparent' => '0,0,0,0',
+ 'turquoise' => '64,224,208',
+ 'violet' => '238,130,238',
+ 'wheat' => '245,222,179',
+ 'white' => '255,255,255',
+ 'whitesmoke' => '245,245,245',
+ 'yellow' => '255,255,0',
+ 'yellowgreen' => '154,205,50'
+ );
+}
+
+/**
+ * SCSS parser
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scss_parser {
+ static protected $precedence = array(
+ "or" => 0,
+ "and" => 1,
+
+ '==' => 2,
+ '!=' => 2,
+ '<=' => 2,
+ '>=' => 2,
+ '=' => 2,
+ '<' => 3,
+ '>' => 2,
+
+ '+' => 3,
+ '-' => 3,
+ '*' => 4,
+ '/' => 4,
+ '%' => 4,
+ );
+
+ static protected $operators = array("+", "-", "*", "/", "%",
+ "==", "!=", "<=", ">=", "<", ">", "and", "or");
+
+ static protected $operatorStr;
+ static protected $whitePattern;
+ static protected $commentMulti;
+
+ static protected $commentSingle = "//";
+ static protected $commentMultiLeft = "/*";
+ static protected $commentMultiRight = "*/";
+
+ public function __construct($sourceName = null, $rootParser = true) {
+ $this->sourceName = $sourceName;
+ $this->rootParser = $rootParser;
+
+ if (empty(self::$operatorStr)) {
+ self::$operatorStr = $this->makeOperatorStr(self::$operators);
+
+ $commentSingle = $this->preg_quote(self::$commentSingle);
+ $commentMultiLeft = $this->preg_quote(self::$commentMultiLeft);
+ $commentMultiRight = $this->preg_quote(self::$commentMultiRight);
+ self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
+ self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
+ }
+ }
+
+ static protected function makeOperatorStr($operators) {
+ return '('.implode('|', array_map(array('scss_parser','preg_quote'),
+ $operators)).')';
+ }
+
+ public function parse($buffer) {
+ $this->count = 0;
+ $this->env = null;
+ $this->inParens = false;
+ $this->pushBlock(null); // root block
+ $this->eatWhiteDefault = true;
+ $this->insertComments = true;
+
+ $this->buffer = $buffer;
+
+ $this->whitespace();
+ while (false !== $this->parseChunk());
+
+ if ($this->count != strlen($this->buffer))
+ $this->throwParseError();
+
+ if (!empty($this->env->parent)) {
+ $this->throwParseError("unclosed block");
+ }
+
+ $this->env->isRoot = true;
+ return $this->env;
+ }
+
+ /**
+ * Parse a single chunk off the head of the buffer and append it to the
+ * current parse environment.
+ *
+ * Returns false when the buffer is empty, or when there is an error.
+ *
+ * This function is called repeatedly until the entire document is
+ * parsed.
+ *
+ * This parser is most similar to a recursive descent parser. Single
+ * functions represent discrete grammatical rules for the language, and
+ * they are able to capture the text that represents those rules.
+ *
+ * Consider the function scssc::keyword(). (All parse functions are
+ * structured the same.)
+ *
+ * The function takes a single reference argument. When calling the
+ * function it will attempt to match a keyword on the head of the buffer.
+ * If it is successful, it will place the keyword in the referenced
+ * argument, advance the position in the buffer, and return true. If it
+ * fails then it won't advance the buffer and it will return false.
+ *
+ * All of these parse functions are powered by scssc::match(), which behaves
+ * the same way, but takes a literal regular expression. Sometimes it is
+ * more convenient to use match instead of creating a new function.
+ *
+ * Because of the format of the functions, to parse an entire string of
+ * grammatical rules, you can chain them together using &&.
+ *
+ * But, if some of the rules in the chain succeed before one fails, then
+ * the buffer position will be left at an invalid state. In order to
+ * avoid this, scssc::seek() is used to remember and set buffer positions.
+ *
+ * Before parsing a chain, use $s = $this->seek() to remember the current
+ * position into $s. Then if a chain fails, use $this->seek($s) to
+ * go back where we started.
+ *
+ * @return boolean
+ */
+ protected function parseChunk() {
+ $s = $this->seek();
+
+ // the directives
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
+ if ($this->literal("@media") && $this->mediaQueryList($mediaQueryList) && $this->literal("{")) {
+ $media = $this->pushSpecialBlock("media");
+ $media->queryList = $mediaQueryList[2];
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@mixin") &&
+ $this->keyword($mixinName) &&
+ ($this->argumentDef($args) || true) &&
+ $this->literal("{"))
+ {
+ $mixin = $this->pushSpecialBlock("mixin");
+ $mixin->name = $mixinName;
+ $mixin->args = $args;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@include") &&
+ $this->keyword($mixinName) &&
+ ($this->literal("(") &&
+ ($this->argValues($argValues) || true) &&
+ $this->literal(")") || true) &&
+ ($this->end() ||
+ $this->literal("{") && $hasBlock = true))
+ {
+ $child = array("include",
+ $mixinName, isset($argValues) ? $argValues : null, null);
+
+ if (!empty($hasBlock)) {
+ $include = $this->pushSpecialBlock("include");
+ $include->child = $child;
+ } else {
+ $this->append($child, $s);
+ }
+
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@import") &&
+ $this->valueList($importPath) &&
+ $this->end())
+ {
+ $this->append(array("import", $importPath), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@extend") &&
+ $this->selectors($selector) &&
+ $this->end())
+ {
+ $this->append(array("extend", $selector), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@function") &&
+ $this->keyword($fnName) &&
+ $this->argumentDef($args) &&
+ $this->literal("{"))
+ {
+ $func = $this->pushSpecialBlock("function");
+ $func->name = $fnName;
+ $func->args = $args;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@return") && $this->valueList($retVal) && $this->end()) {
+ $this->append(array("return", $retVal), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@each") &&
+ $this->variable($varName) &&
+ $this->literal("in") &&
+ $this->valueList($list) &&
+ $this->literal("{"))
+ {
+ $each = $this->pushSpecialBlock("each");
+ $each->var = $varName[1];
+ $each->list = $list;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@while") &&
+ $this->expression($cond) &&
+ $this->literal("{"))
+ {
+ $while = $this->pushSpecialBlock("while");
+ $while->cond = $cond;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@for") &&
+ $this->variable($varName) &&
+ $this->literal("from") &&
+ $this->expression($start) &&
+ ($this->literal("through") ||
+ ($forUntil = true && $this->literal("to"))) &&
+ $this->expression($end) &&
+ $this->literal("{"))
+ {
+ $for = $this->pushSpecialBlock("for");
+ $for->var = $varName[1];
+ $for->start = $start;
+ $for->end = $end;
+ $for->until = isset($forUntil);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@if") && $this->valueList($cond) && $this->literal("{")) {
+ $if = $this->pushSpecialBlock("if");
+ $if->cond = $cond;
+ $if->cases = array();
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if (($this->literal("@debug") || $this->literal("@warn")) &&
+ $this->valueList($value) &&
+ $this->end()) {
+ $this->append(array("debug", $value, $s), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("@content") && $this->end()) {
+ $this->append(array("mixin_content"), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ $last = $this->last();
+ if (!is_null($last) && $last[0] == "if") {
+ list(, $if) = $last;
+ if ($this->literal("@else")) {
+ if ($this->literal("{")) {
+ $else = $this->pushSpecialBlock("else");
+ } elseif ($this->literal("if") && $this->valueList($cond) && $this->literal("{")) {
+ $else = $this->pushSpecialBlock("elseif");
+ $else->cond = $cond;
+ }
+
+ if (isset($else)) {
+ $else->dontAppend = true;
+ $if->cases[] = $else;
+ return true;
+ }
+ }
+
+ $this->seek($s);
+ }
+
+ if ($this->literal("@charset") &&
+ $this->valueList($charset) && $this->end())
+ {
+ $this->append(array("charset", $charset), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // doesn't match built in directive, do generic one
+ if ($this->literal("@", false) && $this->keyword($dirName) &&
+ ($this->openString("{", $dirValue) || true) &&
+ $this->literal("{"))
+ {
+ $directive = $this->pushSpecialBlock("directive");
+ $directive->name = $dirName;
+ if (isset($dirValue)) $directive->value = $dirValue;
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // property shortcut
+ // captures most properties before having to parse a selector
+ if ($this->keyword($name, false) &&
+ $this->literal(": ") &&
+ $this->valueList($value) &&
+ $this->end())
+ {
+ $name = array("string", "", array($name));
+ $this->append(array("assign", $name, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // variable assigns
+ if ($this->variable($name) &&
+ $this->literal(":") &&
+ $this->valueList($value) && $this->end())
+ {
+ // check for !default
+ $defaultVar = $value[0] == "list" && $this->stripDefault($value);
+ $this->append(array("assign", $name, $value, $defaultVar), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // misc
+ if ($this->literal("-->")) {
+ return true;
+ }
+
+ // opening css block
+ $oldComments = $this->insertComments;
+ $this->insertComments = false;
+ if ($this->selectors($selectors) && $this->literal("{")) {
+ $this->pushBlock($selectors);
+ $this->insertComments = $oldComments;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+ $this->insertComments = $oldComments;
+
+ // property assign, or nested assign
+ if ($this->propertyName($name) && $this->literal(":")) {
+ $foundSomething = false;
+ if ($this->valueList($value)) {
+ $this->append(array("assign", $name, $value), $s);
+ $foundSomething = true;
+ }
+
+ if ($this->literal("{")) {
+ $propBlock = $this->pushSpecialBlock("nestedprop");
+ $propBlock->prefix = $name;
+ $foundSomething = true;
+ } elseif ($foundSomething) {
+ $foundSomething = $this->end();
+ }
+
+ if ($foundSomething) {
+ return true;
+ }
+
+ $this->seek($s);
+ } else {
+ $this->seek($s);
+ }
+
+ // closing a block
+ if ($this->literal("}")) {
+ $block = $this->popBlock();
+ if (isset($block->type) && $block->type == "include") {
+ $include = $block->child;
+ unset($block->child);
+ $include[3] = $block;
+ $this->append($include, $s);
+ } elseif (empty($block->dontAppend)) {
+ $type = isset($block->type) ? $block->type : "block";
+ $this->append(array($type, $block), $s);
+ }
+ return true;
+ }
+
+ // extra stuff
+ if ($this->literal(";") ||
+ $this->literal("<!--"))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function stripDefault(&$value) {
+ $def = end($value[2]);
+ if ($def[0] == "keyword" && $def[1] == "!default") {
+ array_pop($value[2]);
+ $value = $this->flattenList($value);
+ return true;
+ }
+
+ if ($def[0] == "list") {
+ return $this->stripDefault($value[2][count($value[2]) - 1]);
+ }
+
+ return false;
+ }
+
+ protected function literal($what, $eatWhitespace = null) {
+ if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
+
+ // shortcut on single letter
+ if (!isset($what[1]) && isset($this->buffer[$this->count])) {
+ if ($this->buffer[$this->count] == $what) {
+ if (!$eatWhitespace) {
+ $this->count++;
+ return true;
+ }
+ // goes below...
+ } else {
+ return false;
+ }
+ }
+
+ return $this->match($this->preg_quote($what), $m, $eatWhitespace);
+ }
+
+ // tree builders
+
+ protected function pushBlock($selectors) {
+ $b = new stdClass;
+ $b->parent = $this->env; // not sure if we need this yet
+
+ $b->selectors = $selectors;
+ $b->children = array();
+
+ $this->env = $b;
+ return $b;
+ }
+
+ protected function pushSpecialBlock($type) {
+ $block = $this->pushBlock(null);
+ $block->type = $type;
+ return $block;
+ }
+
+ protected function popBlock() {
+ if (empty($this->env->parent)) {
+ $this->throwParseError("unexpected }");
+ }
+
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ unset($old->parent);
+ return $old;
+ }
+
+ protected function append($statement, $pos=null) {
+ if ($pos !== null) {
+ $statement[-1] = $pos;
+ if (!$this->rootParser) $statement[-2] = $this;
+ }
+ $this->env->children[] = $statement;
+ }
+
+ // last child that was appended
+ protected function last() {
+ $i = count($this->env->children) - 1;
+ if (isset($this->env->children[$i]))
+ return $this->env->children[$i];
+ }
+
+ // high level parsers (they return parts of ast)
+
+ protected function mediaQueryList(&$out) {
+ return $this->genericList($out, "mediaQuery", ",", false);
+ }
+
+ protected function mediaQuery(&$out) {
+ $s = $this->seek();
+
+ $expressions = null;
+ $parts = array();
+
+ if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->mixedKeyword($mediaType)) {
+ $prop = array("mediaType");
+ if (isset($only)) $prop[] = array("keyword", "only");
+ if (isset($not)) $prop[] = array("keyword", "not");
+ $media = array("list", "", array());
+ foreach ((array)$mediaType as $type) {
+ if (is_array($type)) {
+ $media[2][] = $type;
+ } else {
+ $media[2][] = array("keyword", $type);
+ }
+ }
+ $prop[] = $media;
+ $parts[] = $prop;
+ }
+
+ if (empty($parts) || $this->literal("and")) {
+ $this->genericList($expressions, "mediaExpression", "and", false);
+ if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
+ }
+
+ $out = $parts;
+ return true;
+ }
+
+ protected function mediaExpression(&$out) {
+ $s = $this->seek();
+ $value = null;
+ if ($this->literal("(") &&
+ $this->expression($feature) &&
+ ($this->literal(":") && $this->expression($value) || true) &&
+ $this->literal(")"))
+ {
+ $out = array("mediaExp", $feature);
+ if ($value) $out[] = $value;
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function argValues(&$out) {
+ if ($this->genericList($list, "argValue", ",", false)) {
+ $out = $list[2];
+ return true;
+ }
+ return false;
+ }
+
+ protected function argValue(&$out) {
+ $s = $this->seek();
+
+ $keyword = null;
+ if (!$this->variable($keyword) || !$this->literal(":")) {
+ $this->seek($s);
+ $keyword = null;
+ }
+
+ if ($this->genericList($value, "expression")) {
+ $out = array($keyword, $value, false);
+ $s = $this->seek();
+ if ($this->literal("...")) {
+ $out[2] = true;
+ } else {
+ $this->seek($s);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+
+ protected function valueList(&$out) {
+ return $this->genericList($out, "spaceList", ",");
+ }
+
+ protected function spaceList(&$out) {
+ return $this->genericList($out, "expression");
+ }
+
+ protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
+ $s = $this->seek();
+ $items = array();
+ while ($this->$parseItem($value)) {
+ $items[] = $value;
+ if ($delim) {
+ if (!$this->literal($delim)) break;
+ }
+ }
+
+ if (count($items) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($flatten && count($items) == 1) {
+ $out = $items[0];
+ } else {
+ $out = array("list", $delim, $items);
+ }
+
+ return true;
+ }
+
+ protected function expression(&$out) {
+ $s = $this->seek();
+
+ if ($this->literal("(")) {
+ if ($this->literal(")")) {
+ $out = array("list", "", array());
+ return true;
+ }
+
+ if ($this->valueList($out) && $this->literal(')') && $out[0] == "list") {
+ return true;
+ }
+
+ $this->seek($s);
+ }
+
+ if ($this->value($lhs)) {
+ $out = $this->expHelper($lhs, 0);
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function expHelper($lhs, $minP) {
+ $opstr = self::$operatorStr;
+
+ $ss = $this->seek();
+ $whiteBefore = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+ while ($this->match($opstr, $m) && self::$precedence[$m[1]] >= $minP) {
+ $whiteAfter = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ $op = $m[1];
+
+ // don't turn negative numbers into expressions
+ if ($op == "-" && $whiteBefore) {
+ if (!$whiteAfter) break;
+ }
+
+ if (!$this->value($rhs)) break;
+
+ // peek and see if rhs belongs to next operator
+ if ($this->peek($opstr, $next) && self::$precedence[$next[1]] > self::$precedence[$op]) {
+ $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
+ }
+
+ $lhs = array("exp", $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter);
+ $ss = $this->seek();
+ $whiteBefore = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+ }
+
+ $this->seek($ss);
+ return $lhs;
+ }
+
+ protected function value(&$out) {
+ $s = $this->seek();
+
+ if ($this->literal("not", false) && $this->whitespace() && $this->value($inner)) {
+ $out = array("unary", "not", $inner, $this->inParens);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->literal("+") && $this->value($inner)) {
+ $out = array("unary", "+", $inner, $this->inParens);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // negation
+ if ($this->literal("-", false) &&
+ ($this->variable($inner) ||
+ $this->unit($inner) ||
+ $this->parenValue($inner)))
+ {
+ $out = array("unary", "-", $inner, $this->inParens);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->parenValue($out)) return true;
+ if ($this->interpolation($out)) return true;
+ if ($this->variable($out)) return true;
+ if ($this->color($out)) return true;
+ if ($this->unit($out)) return true;
+ if ($this->string($out)) return true;
+ if ($this->func($out)) return true;
+ if ($this->progid($out)) return true;
+
+ if ($this->keyword($keyword)) {
+ if ($keyword == "null") {
+ $out = array("null");
+ } else {
+ $out = array("keyword", $keyword);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ // value wrappen in parentheses
+ protected function parenValue(&$out) {
+ $s = $this->seek();
+
+ $inParens = $this->inParens;
+ if ($this->literal("(") &&
+ ($this->inParens = true) && $this->expression($exp) &&
+ $this->literal(")"))
+ {
+ $out = $exp;
+ $this->inParens = $inParens;
+ return true;
+ } else {
+ $this->inParens = $inParens;
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ protected function progid(&$out) {
+ $s = $this->seek();
+ if ($this->literal("progid:", false) &&
+ $this->openString("(", $fn) &&
+ $this->literal("("))
+ {
+ $this->openString(")", $args, "(");
+ if ($this->literal(")")) {
+ $out = array("string", "", array(
+ "progid:", $fn, "(", $args, ")"
+ ));
+ return true;
+ }
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function func(&$func) {
+ $s = $this->seek();
+
+ if ($this->keyword($name, false) &&
+ $this->literal("("))
+ {
+ if ($name == "alpha" && $this->argumentList($args)) {
+ $func = array("function", $name, array("string", "", $args));
+ return true;
+ }
+
+ if ($name != "expression" && !preg_match("/^(-[a-z]+-)?calc$/", $name)) {
+ $ss = $this->seek();
+ if ($this->argValues($args) && $this->literal(")")) {
+ $func = array("fncall", $name, $args);
+ return true;
+ }
+ $this->seek($ss);
+ }
+
+ if (($this->openString(")", $str, "(") || true ) &&
+ $this->literal(")"))
+ {
+ $args = array();
+ if (!empty($str)) {
+ $args[] = array(null, array("string", "", array($str)));
+ }
+
+ $func = array("fncall", $name, $args);
+ return true;
+ }
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function argumentList(&$out) {
+ $s = $this->seek();
+ $this->literal("(");
+
+ $args = array();
+ while ($this->keyword($var)) {
+ $ss = $this->seek();
+
+ if ($this->literal("=") && $this->expression($exp)) {
+ $args[] = array("string", "", array($var."="));
+ $arg = $exp;
+ } else {
+ break;
+ }
+
+ $args[] = $arg;
+
+ if (!$this->literal(",")) break;
+
+ $args[] = array("string", "", array(", "));
+ }
+
+ if (!$this->literal(")") || !count($args)) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $args;
+ return true;
+ }
+
+ protected function argumentDef(&$out) {
+ $s = $this->seek();
+ $this->literal("(");
+
+ $args = array();
+ while ($this->variable($var)) {
+ $arg = array($var[1], null, false);
+
+ $ss = $this->seek();
+ if ($this->literal(":") && $this->genericList($defaultVal, "expression")) {
+ $arg[1] = $defaultVal;
+ } else {
+ $this->seek($ss);
+ }
+
+ $ss = $this->seek();
+ if ($this->literal("...")) {
+ $sss = $this->seek();
+ if (!$this->literal(")")) {
+ $this->throwParseError("... has to be after the final argument");
+ }
+ $arg[2] = true;
+ $this->seek($sss);
+ } else {
+ $this->seek($ss);
+ }
+
+ $args[] = $arg;
+ if (!$this->literal(",")) break;
+ }
+
+ if (!$this->literal(")")) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $args;
+ return true;
+ }
+
+ protected function color(&$out) {
+ $color = array('color');
+
+ if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
+ if (isset($m[3])) {
+ $num = $m[3];
+ $width = 16;
+ } else {
+ $num = $m[2];
+ $width = 256;
+ }
+
+ $num = hexdec($num);
+ foreach (array(3,2,1) as $i) {
+ $t = $num % $width;
+ $num /= $width;
+
+ $color[$i] = $t * (256/$width) + $t * floor(16/$width);
+ }
+
+ $out = $color;
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function unit(&$unit) {
+ if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
+ $unit = array("number", $m[1], empty($m[3]) ? "" : $m[3]);
+ return true;
+ }
+ return false;
+ }
+
+ protected function string(&$out) {
+ $s = $this->seek();
+ if ($this->literal('"', false)) {
+ $delim = '"';
+ } elseif ($this->literal("'", false)) {
+ $delim = "'";
+ } else {
+ return false;
+ }
+
+ $content = array();
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while ($this->matchString($m, $delim)) {
+ $content[] = $m[1];
+ if ($m[2] == "#{") {
+ $this->count -= strlen($m[2]);
+ if ($this->interpolation($inter, false)) {
+ $content[] = $inter;
+ } else {
+ $this->count += strlen($m[2]);
+ $content[] = "#{"; // ignore it
+ }
+ } elseif ($m[2] == '\\') {
+ $content[] = $m[2];
+ if ($this->literal($delim, false)) {
+ $content[] = $delim;
+ }
+ } else {
+ $this->count -= strlen($delim);
+ break; // delim
+ }
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if ($this->literal($delim)) {
+ $out = array("string", $delim, $content);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function mixedKeyword(&$out) {
+ $s = $this->seek();
+
+ $parts = array();
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while (true) {
+ if ($this->keyword($key)) {
+ $parts[] = $key;
+ continue;
+ }
+
+ if ($this->interpolation($inter)) {
+ $parts[] = $inter;
+ continue;
+ }
+
+ break;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($parts) == 0) return false;
+
+ if ($this->eatWhiteDefault) {
+ $this->whitespace();
+ }
+
+ $out = $parts;
+ return true;
+ }
+
+ // an unbounded string stopped by $end
+ protected function openString($end, &$out, $nestingOpen=null) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ $stop = array("'", '"', "#{", $end);
+ $stop = array_map(array($this, "preg_quote"), $stop);
+ $stop[] = self::$commentMulti;
+
+ $patt = '(.*?)('.implode("|", $stop).')';
+
+ $nestingLevel = 0;
+
+ $content = array();
+ while ($this->match($patt, $m, false)) {
+ if (isset($m[1]) && $m[1] !== '') {
+ $content[] = $m[1];
+ if ($nestingOpen) {
+ $nestingLevel += substr_count($m[1], $nestingOpen);
+ }
+ }
+
+ $tok = $m[2];
+
+ $this->count-= strlen($tok);
+ if ($tok == $end) {
+ if ($nestingLevel == 0) {
+ break;
+ } else {
+ $nestingLevel--;
+ }
+ }
+
+ if (($tok == "'" || $tok == '"') && $this->string($str)) {
+ $content[] = $str;
+ continue;
+ }
+
+ if ($tok == "#{" && $this->interpolation($inter)) {
+ $content[] = $inter;
+ continue;
+ }
+
+ $content[] = $tok;
+ $this->count+= strlen($tok);
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($content) == 0) return false;
+
+ // trim the end
+ if (is_string(end($content))) {
+ $content[count($content) - 1] = rtrim(end($content));
+ }
+
+ $out = array("string", "", $content);
+ return true;
+ }
+
+ // $lookWhite: save information about whitespace before and after
+ protected function interpolation(&$out, $lookWhite=true) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = true;
+
+ $s = $this->seek();
+ if ($this->literal("#{") && $this->valueList($value) && $this->literal("}", false)) {
+
+ // TODO: don't error if out of bounds
+
+ if ($lookWhite) {
+ $left = preg_match('/\s/', $this->buffer[$s - 1]) ? " " : "";
+ $right = preg_match('/\s/', $this->buffer[$this->count]) ? " ": "";
+ } else {
+ $left = $right = false;
+ }
+
+ $out = array("interpolate", $value, $left, $right);
+ $this->eatWhiteDefault = $oldWhite;
+ if ($this->eatWhiteDefault) $this->whitespace();
+ return true;
+ }
+
+ $this->seek($s);
+ $this->eatWhiteDefault = $oldWhite;
+ return false;
+ }
+
+ // low level parsers
+
+ // returns an array of parts or a string
+ protected function propertyName(&$out) {
+ $s = $this->seek();
+ $parts = array();
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while (true) {
+ if ($this->interpolation($inter)) {
+ $parts[] = $inter;
+ } elseif ($this->keyword($text)) {
+ $parts[] = $text;
+ } elseif (count($parts) == 0 && $this->match('[:.#]', $m, false)) {
+ // css hacks
+ $parts[] = $m[0];
+ } else {
+ break;
+ }
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ if (count($parts) == 0) return false;
+
+ // match comment hack
+ if (preg_match(self::$whitePattern,
+ $this->buffer, $m, null, $this->count))
+ {
+ if (!empty($m[0])) {
+ $parts[] = $m[0];
+ $this->count += strlen($m[0]);
+ }
+ }
+
+ $this->whitespace(); // get any extra whitespace
+
+ $out = array("string", "", $parts);
+ return true;
+ }
+
+ // comma separated list of selectors
+ protected function selectors(&$out) {
+ $s = $this->seek();
+ $selectors = array();
+ while ($this->selector($sel)) {
+ $selectors[] = $sel;
+ if (!$this->literal(",")) break;
+ while ($this->literal(",")); // ignore extra
+ }
+
+ if (count($selectors) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $selectors;
+ return true;
+ }
+
+ // whitespace separated list of selectorSingle
+ protected function selector(&$out) {
+ $selector = array();
+
+ while (true) {
+ if ($this->match('[>+~]+', $m)) {
+ $selector[] = array($m[0]);
+ } elseif ($this->selectorSingle($part)) {
+ $selector[] = $part;
+ $this->whitespace();
+ } elseif ($this->match('\/[^\/]+\/', $m)) {
+ $selector[] = array($m[0]);
+ } else {
+ break;
+ }
+
+ }
+
+ if (count($selector) == 0) {
+ return false;
+ }
+
+ $out = $selector;
+ return true;
+ }
+
+ // the parts that make up
+ // div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
+ protected function selectorSingle(&$out) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ $parts = array();
+
+ if ($this->literal("*", false)) {
+ $parts[] = "*";
+ }
+
+ while (true) {
+ // see if we can stop early
+ if ($this->match("\s*[{,]", $m)) {
+ $this->count--;
+ break;
+ }
+
+ $s = $this->seek();
+ // self
+ if ($this->literal("&", false)) {
+ $parts[] = scssc::$selfSelector;
+ continue;
+ }
+
+ if ($this->literal(".", false)) {
+ $parts[] = ".";
+ continue;
+ }
+
+ if ($this->literal("|", false)) {
+ $parts[] = "|";
+ continue;
+ }
+
+ // for keyframes
+ if ($this->unit($unit)) {
+ $parts[] = $unit;
+ continue;
+ }
+
+ if ($this->keyword($name)) {
+ $parts[] = $name;
+ continue;
+ }
+
+ if ($this->interpolation($inter)) {
+ $parts[] = $inter;
+ continue;
+ }
+
+ if ($this->literal('%', false) && $this->placeholder($placeholder)) {
+ $parts[] = '%';
+ $parts[] = $placeholder;
+ continue;
+ }
+
+ if ($this->literal("#", false)) {
+ $parts[] = "#";
+ continue;
+ }
+
+ // a pseudo selector
+ if ($this->match("::?", $m) && $this->mixedKeyword($nameParts)) {
+ $parts[] = $m[0];
+ foreach ($nameParts as $sub) {
+ $parts[] = $sub;
+ }
+
+ $ss = $this->seek();
+ if ($this->literal("(") &&
+ ($this->openString(")", $str, "(") || true ) &&
+ $this->literal(")"))
+ {
+ $parts[] = "(";
+ if (!empty($str)) $parts[] = $str;
+ $parts[] = ")";
+ } else {
+ $this->seek($ss);
+ }
+
+ continue;
+ } else {
+ $this->seek($s);
+ }
+
+ // attribute selector
+ // TODO: replace with open string?
+ if ($this->literal("[", false)) {
+ $attrParts = array("[");
+ // keyword, string, operator
+ while (true) {
+ if ($this->literal("]", false)) {
+ $this->count--;
+ break; // get out early
+ }
+
+ if ($this->match('\s+', $m)) {
+ $attrParts[] = " ";
+ continue;
+ }
+ if ($this->string($str)) {
+ $attrParts[] = $str;
+ continue;
+ }
+
+ if ($this->keyword($word)) {
+ $attrParts[] = $word;
+ continue;
+ }
+
+ if ($this->interpolation($inter, false)) {
+ $attrParts[] = $inter;
+ continue;
+ }
+
+ // operator, handles attr namespace too
+ if ($this->match('[|-~\$\*\^=]+', $m)) {
+ $attrParts[] = $m[0];
+ continue;
+ }
+
+ break;
+ }
+
+ if ($this->literal("]", false)) {
+ $attrParts[] = "]";
+ foreach ($attrParts as $part) {
+ $parts[] = $part;
+ }
+ continue;
+ }
+ $this->seek($s);
+ // should just break here?
+ }
+
+ break;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($parts) == 0) return false;
+
+ $out = $parts;
+ return true;
+ }
+
+ protected function variable(&$out) {
+ $s = $this->seek();
+ if ($this->literal("$", false) && $this->keyword($name)) {
+ $out = array("var", $name);
+ return true;
+ }
+ $this->seek($s);
+ return false;
+ }
+
+ protected function keyword(&$word, $eatWhitespace = null) {
+ if ($this->match('([\w_\-\*!"\'\\\\][\w\-_"\'\\\\]*)',
+ $m, $eatWhitespace))
+ {
+ $word = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ protected function placeholder(&$placeholder) {
+ if ($this->match('([\w\-_]+)', $m)) {
+ $placeholder = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ // consume an end of statement delimiter
+ protected function end() {
+ if ($this->literal(';')) {
+ return true;
+ } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
+ // if there is end of file or a closing block next then we don't need a ;
+ return true;
+ }
+ return false;
+ }
+
+ // advance counter to next occurrence of $what
+ // $until - don't include $what in advance
+ // $allowNewline, if string, will be used as valid char set
+ protected function to($what, &$out, $until = false, $allowNewline = false) {
+ if (is_string($allowNewline)) {
+ $validChars = $allowNewline;
+ } else {
+ $validChars = $allowNewline ? "." : "[^\n]";
+ }
+ if (!$this->match('('.$validChars.'*?)'.$this->preg_quote($what), $m, !$until)) return false;
+ if ($until) $this->count -= strlen($what); // give back $what
+ $out = $m[1];
+ return true;
+ }
+
+ public function throwParseError($msg = "parse error", $count = null) {
+ $count = is_null($count) ? $this->count : $count;
+
+ $line = $this->getLineNo($count);
+
+ if (!empty($this->sourceName)) {
+ $loc = "$this->sourceName on line $line";
+ } else {
+ $loc = "line: $line";
+ }
+
+ if ($this->peek("(.*?)(\n|$)", $m, $count)) {
+ throw new Exception("$msg: failed at `$m[1]` $loc");
+ } else {
+ throw new Exception("$msg: $loc");
+ }
+ }
+
+ public function getLineNo($pos) {
+ return 1 + substr_count(substr($this->buffer, 0, $pos), "\n");
+ }
+
+ /**
+ * Match string looking for either ending delim, escape, or string interpolation
+ *
+ * {@internal This is a workaround for preg_match's 250K string match limit. }}
+ *
+ * @param array $m Matches (passed by reference)
+ * @param string $delim Delimeter
+ *
+ * @return boolean True if match; false otherwise
+ */
+ protected function matchString(&$m, $delim) {
+ $token = null;
+
+ $end = strpos($this->buffer, "\n", $this->count);
+ if ($end === false) {
+ $end = strlen($this->buffer);
+ }
+
+ // look for either ending delim, escape, or string interpolation
+ foreach (array('#{', '\\', $delim) as $lookahead) {
+ $pos = strpos($this->buffer, $lookahead, $this->count);
+ if ($pos !== false && $pos < $end) {
+ $end = $pos;
+ $token = $lookahead;
+ }
+ }
+
+ if (!isset($token)) {
+ return false;
+ }
+
+ $match = substr($this->buffer, $this->count, $end - $this->count);
+ $m = array(
+ $match . $token,
+ $match,
+ $token
+ );
+ $this->count = $end + strlen($token);
+
+ return true;
+ }
+
+ // try to match something on head of buffer
+ protected function match($regex, &$out, $eatWhitespace = null) {
+ if (is_null($eatWhitespace)) $eatWhitespace = $this->eatWhiteDefault;
+
+ $r = '/'.$regex.'/Ais';
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+ $this->count += strlen($out[0]);
+ if ($eatWhitespace) $this->whitespace();
+ return true;
+ }
+ return false;
+ }
+
+ // match some whitespace
+ protected function whitespace() {
+ $gotWhite = false;
+ while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
+ if ($this->insertComments) {
+ if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
+ $this->append(array("comment", $m[1]));
+ $this->commentsSeen[$this->count] = true;
+ }
+ }
+ $this->count += strlen($m[0]);
+ $gotWhite = true;
+ }
+ return $gotWhite;
+ }
+
+ protected function peek($regex, &$out, $from=null) {
+ if (is_null($from)) $from = $this->count;
+
+ $r = '/'.$regex.'/Ais';
+ $result = preg_match($r, $this->buffer, $out, null, $from);
+
+ return $result;
+ }
+
+ protected function seek($where = null) {
+ if ($where === null) return $this->count;
+ else $this->count = $where;
+ return true;
+ }
+
+ static function preg_quote($what) {
+ return preg_quote($what, '/');
+ }
+
+ protected function show() {
+ if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
+ return $m[1];
+ }
+ return "";
+ }
+
+ // turn list of length 1 into value type
+ protected function flattenList($value) {
+ if ($value[0] == "list" && count($value[2]) == 1) {
+ return $this->flattenList($value[2][0]);
+ }
+ return $value;
+ }
+}
+
+/**
+ * SCSS base formatter
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scss_formatter {
+ public $indentChar = " ";
+
+ public $break = "\n";
+ public $open = " {";
+ public $close = "}";
+ public $tagSeparator = ", ";
+ public $assignSeparator = ": ";
+
+ public function __construct() {
+ $this->indentLevel = 0;
+ }
+
+ public function indentStr($n = 0) {
+ return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
+ }
+
+ public function property($name, $value) {
+ return $name . $this->assignSeparator . $value . ";";
+ }
+
+ protected function block($block) {
+ if (empty($block->lines) && empty($block->children)) return;
+
+ $inner = $pre = $this->indentStr();
+
+ if (!empty($block->selectors)) {
+ echo $pre .
+ implode($this->tagSeparator, $block->selectors) .
+ $this->open . $this->break;
+ $this->indentLevel++;
+ $inner = $this->indentStr();
+ }
+
+ if (!empty($block->lines)) {
+ $glue = $this->break.$inner;
+ echo $inner . implode($glue, $block->lines);
+ if (!empty($block->children)) {
+ echo $this->break;
+ }
+ }
+
+ foreach ($block->children as $child) {
+ $this->block($child);
+ }
+
+ if (!empty($block->selectors)) {
+ $this->indentLevel--;
+ if (empty($block->children)) echo $this->break;
+ echo $pre . $this->close . $this->break;
+ }
+ }
+
+ public function format($block) {
+ ob_start();
+ $this->block($block);
+ $out = ob_get_clean();
+
+ return $out;
+ }
+}
+
+/**
+ * SCSS nested formatter
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scss_formatter_nested extends scss_formatter {
+ public $close = " }";
+
+ // adjust the depths of all children, depth first
+ public function adjustAllChildren($block) {
+ // flatten empty nested blocks
+ $children = array();
+ foreach ($block->children as $i => $child) {
+ if (empty($child->lines) && empty($child->children)) {
+ if (isset($block->children[$i + 1])) {
+ $block->children[$i + 1]->depth = $child->depth;
+ }
+ continue;
+ }
+ $children[] = $child;
+ }
+
+ $count = count($children);
+ for ($i = 0; $i < $count; $i++) {
+ $depth = $children[$i]->depth;
+ $j = $i + 1;
+ if (isset($children[$j]) && $depth < $children[$j]->depth) {
+ $childDepth = $children[$j]->depth;
+ for (; $j < $count; $j++) {
+ if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
+ $children[$j]->depth = $depth + 1;
+ }
+ }
+ }
+ }
+
+ $block->children = $children;
+
+ // make relative to parent
+ foreach ($block->children as $child) {
+ $this->adjustAllChildren($child);
+ $child->depth = $child->depth - $block->depth;
+ }
+ }
+
+ protected function block($block) {
+ if ($block->type == "root") {
+ $this->adjustAllChildren($block);
+ }
+
+ $inner = $pre = $this->indentStr($block->depth - 1);
+ if (!empty($block->selectors)) {
+ echo $pre .
+ implode($this->tagSeparator, $block->selectors) .
+ $this->open . $this->break;
+ $this->indentLevel++;
+ $inner = $this->indentStr($block->depth - 1);
+ }
+
+ if (!empty($block->lines)) {
+ $glue = $this->break.$inner;
+ echo $inner . implode($glue, $block->lines);
+ if (!empty($block->children)) echo $this->break;
+ }
+
+ foreach ($block->children as $i => $child) {
+ // echo "*** block: ".$block->depth." child: ".$child->depth."\n";
+ $this->block($child);
+ if ($i < count($block->children) - 1) {
+ echo $this->break;
+
+ if (isset($block->children[$i + 1])) {
+ $next = $block->children[$i + 1];
+ if ($next->depth == max($block->depth, 1) && $child->depth >= $next->depth) {
+ echo $this->break;
+ }
+ }
+ }
+ }
+
+ if (!empty($block->selectors)) {
+ $this->indentLevel--;
+ echo $this->close;
+ }
+
+ if ($block->type == "root") {
+ echo $this->break;
+ }
+ }
+}
+
+/**
+ * SCSS compressed formatter
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scss_formatter_compressed extends scss_formatter {
+ public $open = "{";
+ public $tagSeparator = ",";
+ public $assignSeparator = ":";
+ public $break = "";
+
+ public function indentStr($n = 0) {
+ return "";
+ }
+}
+
+/**
+ * SCSS server
+ *
+ * @author Leaf Corcoran <leafot@gmail.com>
+ */
+class scss_server {
+ /**
+ * Join path components
+ *
+ * @param string $left Path component, left of the directory separator
+ * @param string $right Path component, right of the directory separator
+ *
+ * @return string
+ */
+ protected function join($left, $right) {
+ return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
+ }
+
+ /**
+ * Get name of requested .scss file
+ *
+ * @return string|null
+ */
+ protected function inputName() {
+ switch (true) {
+ case isset($_GET['p']):
+ return $_GET['p'];
+ case isset($_SERVER['PATH_INFO']):
+ return $_SERVER['PATH_INFO'];
+ case isset($_SERVER['DOCUMENT_URI']):
+ return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
+ }
+ }
+
+ /**
+ * Get path to requested .scss file
+ *
+ * @return string
+ */
+ protected function findInput() {
+ if (($input = $this->inputName())
+ && strpos($input, '..') === false
+ && substr($input, -5) === '.scss'
+ ) {
+ $name = $this->join($this->dir, $input);
+
+ if (is_file($name) && is_readable($name)) {
+ return $name;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get path to cached .css file
+ *
+ * @return string
+ */
+ protected function cacheName($fname) {
+ return $this->join($this->cacheDir, md5($fname) . '.css');
+ }
+
+ /**
+ * Get path to cached imports
+ *
+ * @return string
+ */
+ protected function importsCacheName($out) {
+ return $out . '.imports';
+ }
+
+ /**
+ * Determine whether .scss file needs to be re-compiled.
+ *
+ * @param string $in Input path
+ * @param string $out Output path
+ *
+ * @return boolean True if compile required.
+ */
+ protected function needsCompile($in, $out) {
+ if (!is_file($out)) return true;
+
+ $mtime = filemtime($out);
+ if (filemtime($in) > $mtime) return true;
+
+ // look for modified imports
+ $icache = $this->importsCacheName($out);
+ if (is_readable($icache)) {
+ $imports = unserialize(file_get_contents($icache));
+ foreach ($imports as $import) {
+ if (filemtime($import) > $mtime) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compile .scss file
+ *
+ * @param string $in Input path (.scss)
+ * @param string $out Output path (.css)
+ *
+ * @return string
+ */
+ protected function compile($in, $out) {
+ $start = microtime(true);
+ $css = $this->scss->compile(file_get_contents($in), $in);
+ $elapsed = round((microtime(true) - $start), 4);
+
+ $v = scssc::$VERSION;
+ $t = date('r');
+ $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
+
+ file_put_contents($out, $css);
+ file_put_contents($this->importsCacheName($out),
+ serialize($this->scss->getParsedFiles()));
+ return $css;
+ }
+
+ /**
+ * Compile requested scss and serve css. Outputs HTTP response.
+ *
+ * @param string $salt Prefix a string to the filename for creating the cache name hash
+ */
+ public function serve($salt = '') {
+ if ($input = $this->findInput()) {
+ $output = $this->cacheName($salt . $input);
+ header('Content-type: text/css');
+
+ if ($this->needsCompile($input, $output)) {
+ try {
+ echo $this->compile($input, $output);
+ } catch (Exception $e) {
+ header('HTTP/1.1 500 Internal Server Error');
+ echo 'Parse error: ' . $e->getMessage() . "\n";
+ }
+ } else {
+ header('X-SCSS-Cache: true');
+ echo file_get_contents($output);
+ }
+
+ return;
+ }
+
+ header('HTTP/1.0 404 Not Found');
+ header('Content-type: text');
+ $v = scssc::$VERSION;
+ echo "/* INPUT NOT FOUND scss $v */\n";
+ }
+
+ /**
+ * Constructor
+ *
+ * @param string $dir Root directory to .scss files
+ * @param string $cacheDir Cache directory
+ * @param \scssc|null $scss SCSS compiler instance
+ */
+ public function __construct($dir, $cacheDir=null, $scss=null) {
+ $this->dir = $dir;
+
+ if (is_null($cacheDir)) {
+ $cacheDir = $this->join($dir, 'scss_cache');
+ }
+
+ $this->cacheDir = $cacheDir;
+ if (!is_dir($this->cacheDir)) mkdir($this->cacheDir, 0755, true);
+
+ if (is_null($scss)) {
+ $scss = new scssc();
+ $scss->setImportPaths($this->dir);
+ }
+ $this->scss = $scss;
+ }
+
+ /**
+ * Helper method to serve compiled scss
+ *
+ * @param string $path Root path
+ */
+ static public function serveFrom($path) {
+ $server = new self($path);
+ $server->serve();
+ }
+}
diff --git a/plugins/jetpack/modules/custom-css/migrate-to-core.php b/plugins/jetpack/modules/custom-css/migrate-to-core.php
new file mode 100644
index 00000000..26108941
--- /dev/null
+++ b/plugins/jetpack/modules/custom-css/migrate-to-core.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Migration from Jetpack Custom CSS to WordPress' Core CSS.
+ *
+ * @since 4.4.2
+ *
+ * @package Jetpack
+ */
+
+/**
+ * Class Jetpack_Custom_CSS_Data_Migration
+ */
+class Jetpack_Custom_CSS_Data_Migration {
+ /**
+ * Set up assorted actions and filters used by this class.
+ */
+ public static function add_hooks() {
+ add_action( 'init', array( __CLASS__, 'register_legacy_post_type' ) );
+ add_action( 'admin_init', array( __CLASS__, 'do_migration' ) );
+
+ include_once( dirname( __FILE__ ) . '/custom-css.php' );
+ if ( ! is_admin() ) {
+ add_action( 'init', array( 'Jetpack_Custom_CSS', 'init' ) );
+ }
+ }
+
+ /**
+ * Do the bulk of the migration.
+ *
+ * @return int|null
+ */
+ public static function do_migration() {
+ Jetpack_Options::update_option( 'custom_css_4.7_migration', true );
+ Jetpack::log( 'custom_css_4.7_migration', 'start' );
+
+ if ( ! post_type_exists( 'safecss' ) ) {
+ self::register_legacy_post_type();
+ }
+
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $preprocessors = apply_filters( 'jetpack_custom_css_preprocessors', array() );
+ $core_css_post = wp_get_custom_css_post();
+ $jetpack_css_post = self::get_post();
+
+ if ( ! $jetpack_css_post ) {
+ return;
+ }
+
+ $revisions = self::get_all_revisions();
+
+ // Migrate the settings from revision meta to theme mod.
+ $options = self::get_options( $jetpack_css_post->ID );
+ set_theme_mod( 'jetpack_custom_css', $options );
+
+ if ( empty( $revisions ) || ! is_array( $revisions ) ) {
+ if ( $jetpack_css_post instanceof WP_Post ) {
+ // Feed in the raw, if the current setting is Sass/LESS, it'll filter it inside.
+ kses_remove_filters();
+ wp_update_custom_css_post( $jetpack_css_post->post_content );
+ kses_init();
+ return 1;
+ }
+ return null;
+ }
+
+ $revisions = array_reverse( $revisions );
+ $themes = Jetpack_Custom_CSS_Enhancements::get_themes();
+ $migrated = array();
+
+ foreach ( $revisions as $post_id => $post ) {
+ // Jetpack had stored the theme Name, not the stylesheet directory, for ... reasons.
+ // Get the stylesheet. If null, the theme is no longer available. Skip.
+ $stylesheet = isset( $themes[ $post->post_excerpt ] ) ? $themes[ $post->post_excerpt ] : null;
+ if ( empty( $stylesheet ) ) {
+ continue;
+ }
+
+ $migrated[] = $post->ID;
+ $preprocessor = get_post_meta( $post->ID, 'custom_css_preprocessor', true );
+ $css = $post->post_content;
+ $pre = '';
+
+ // Do a revision by revision parsing.
+ if ( $preprocessor && isset( $preprocessors[ $preprocessor ] ) ) {
+ $pre = $css;
+ $css = call_user_func( $preprocessors[ $preprocessor ]['callback'], $pre );
+ }
+
+ kses_remove_filters();
+ wp_update_custom_css_post( $css, array(
+ 'stylesheet' => $stylesheet,
+ 'preprocessed' => $pre,
+ ) );
+ kses_init();
+ }
+
+ // If we've migrated some CSS for the current theme and there was already something there in the Core dataset ...
+ if ( $core_css_post && $jetpack_css_post ) {
+ $preprocessor = $options['preprocessor'];
+
+ $css = $core_css_post->post_content;
+ $pre = $core_css_post->post_content_filtered;
+ if ( $preprocessor ) {
+ if ( $pre ) {
+ $pre .= "\r\n\r\n/*\r\n\t" . esc_js( __( 'CSS Migrated from Jetpack:', 'jetpack' ) ) . "\r\n*/\r\n\r\n";
+ }
+ $pre .= $jetpack_css_post->post_content;
+
+ $css .= "\r\n\r\n/*\r\n\t" . esc_js( __( 'CSS Migrated from Jetpack:', 'jetpack' ) ) . "\r\n*/\r\n\r\n";
+ $css .= call_user_func( $preprocessors[ $preprocessor ]['callback'], $jetpack_css_post->post_content );
+ } else {
+ $css .= "\r\n\r\n/*\r\n\t" . esc_js( __( 'CSS Migrated from Jetpack:', 'jetpack' ) ) . "\r\n*/\r\n\r\n";
+ $css .= $jetpack_css_post->post_content;
+ }
+
+ wp_update_custom_css_post( $css, array(
+ 'preprocessed' => $pre,
+ ) );
+ }
+
+ Jetpack::log( 'custom_css_4.7_migration', count( $migrated ) . 'revisions migrated' );
+ return count( $migrated );
+ }
+
+ /**
+ * Re-register the legacy CPT so we can play with the content already in the database.
+ */
+ public static function register_legacy_post_type() {
+ if ( post_type_exists( 'safecss' ) ) {
+ return;
+ }
+ // Register safecss as a custom post_type
+ // Explicit capability definitions are largely unnecessary because the posts are manipulated in code via an options page, managing CSS revisions does check the capabilities, so let's ensure that the proper caps are checked.
+ register_post_type( 'safecss', array(
+ 'label' => 'Custom CSS',
+ 'supports' => array( 'revisions' ),
+ 'can_export' => false,
+ 'rewrite' => false,
+ 'capabilities' => array(
+ 'edit_post' => 'edit_theme_options',
+ 'read_post' => 'read',
+ 'delete_post' => 'edit_theme_options',
+ 'edit_posts' => 'edit_theme_options',
+ 'edit_others_posts' => 'edit_theme_options',
+ 'publish_posts' => 'edit_theme_options',
+ 'read_private_posts' => 'read',
+ ),
+ ) );
+ }
+
+ /**
+ * Get the post used for legacy storage.
+ *
+ * Jetpack used to use a single post for all themes, just blanking it on theme switch. This gets that post.
+ *
+ * @return array|bool|null|WP_Post
+ */
+ public static function get_post() {
+ /** This filter is documented in modules/custom-css/custom-css.php */
+ $custom_css_post_id = apply_filters( 'jetpack_custom_css_pre_post_id', null );
+ if ( ! is_null( $custom_css_post_id ) ) {
+ return get_post( $custom_css_post_id );
+ }
+
+ $custom_css_post_id = wp_cache_get( 'custom_css_post_id' );
+
+ if ( false === $custom_css_post_id ) {
+ $custom_css_posts = get_posts( array(
+ 'posts_per_page' => 1,
+ 'post_type' => 'safecss',
+ 'post_status' => 'publish',
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ ) );
+
+ $custom_css_post_id = 0;
+ if ( count( $custom_css_posts ) > 0 ) {
+ $custom_css_post_id = $custom_css_posts[0]->ID;
+ }
+
+ // Save post_id=0 to note that no safecss post exists.
+ wp_cache_set( 'custom_css_post_id', $custom_css_post_id );
+ }
+
+ if ( ! $custom_css_post_id ) {
+ return false;
+ }
+
+ return get_post( $custom_css_post_id );
+ }
+
+ /**
+ * Get all revisions of the Jetpack CSS CPT entry.
+ *
+ * @return array
+ */
+ public static function get_all_revisions() {
+ $post = self::get_post();
+
+ if ( ! $post ) {
+ return array();
+ }
+
+ $revisions = wp_get_post_revisions( $post->ID, array(
+ 'posts_per_page' => -1,
+ 'orderby' => 'date',
+ 'order' => 'DESC',
+ ) );
+
+ return $revisions;
+ }
+
+ /**
+ * Get the options stored for a given revision ID.
+ *
+ * Jetpack used to version the settings by storing them as meta on the revision.
+ *
+ * @param integer $post_id Post ID.
+ *
+ * @return array
+ */
+ public static function get_options( $post_id = null ) {
+ if ( empty( $post_id ) ) {
+ $post = self::get_post();
+ $post_id = $post->ID;
+ }
+
+ $meta = get_post_meta( $post_id );
+
+ $replace = false;
+ if ( isset( $meta['custom_css_add'][0] ) && 'no' === $meta['custom_css_add'][0] ) {
+ $replace = true;
+ }
+
+ return array(
+ 'preprocessor' => isset( $meta['custom_css_preprocessor'][0] ) ? $meta['custom_css_preprocessor'][0] : '',
+ 'replace' => $replace,
+ 'content_width' => isset( $meta['content_width'][0] ) ? $meta['content_width'][0] : '',
+ );
+ }
+}
+
+Jetpack_Custom_CSS_Data_Migration::add_hooks();