mirror of
https://github.com/Rushilwiz/dear-anon.git
synced 2025-04-21 20:39:51 -04:00
2205 lines
97 KiB
PHP
2205 lines
97 KiB
PHP
<?php
|
|
// Exit if accessed directly
|
|
if ( !defined( 'ABSPATH' ) ) exit;
|
|
|
|
/*
|
|
Class: ChildThemeConfiguratorCSS
|
|
Plugin URI: http://www.childthemeconfigurator.com/
|
|
Description: Handles all CSS input, output, parsing, normalization and storage
|
|
Version: 2.5.6
|
|
Author: Lilaea Media
|
|
Author URI: http://www.lilaeamedia.com/
|
|
Text Domain: chld_thm_cfg
|
|
Domain Path: /lang
|
|
License: GPLv2
|
|
Copyright (C) 2014-2018 Lilaea Media
|
|
*/
|
|
|
|
class ChildThemeConfiguratorCSS {
|
|
|
|
// data dictionaries
|
|
var $dict_query; // @media queries and 'base'
|
|
var $dict_sel; // selectors
|
|
var $dict_qs; // query/selector lookup
|
|
var $dict_rule; // css rules
|
|
var $dict_val; // css values
|
|
var $dict_seq; // child load order (priority)
|
|
var $dict_token;
|
|
// hierarchies
|
|
var $val_ndx; // selector => rule => value hierarchy
|
|
// key counters
|
|
var $qskey; // counter for dict_qs
|
|
var $querykey; // counter for dict_query
|
|
var $selkey; // counter for dict_sel
|
|
var $rulekey; // counter for dict_rule
|
|
var $valkey; // counter for dict_val
|
|
var $tokenkey; // counter for dict_token
|
|
|
|
// from parent/child form
|
|
var $child; // child theme slug
|
|
var $parnt; // parent theme slug
|
|
var $configtype; // legacy plugin slug
|
|
var $handling; // child stylesheet handling option
|
|
var $enqueue; // whether or not to load parent theme
|
|
var $ignoreparnt; // no not parse or enqueue parent
|
|
var $qpriority;
|
|
var $mpriority;
|
|
var $hasstyles;
|
|
var $parntloaded;
|
|
var $childloaded;
|
|
var $parnt_deps; //
|
|
var $child_deps; //
|
|
var $forcedep;
|
|
var $swappath;
|
|
var $addl_css;
|
|
var $cssunreg;
|
|
var $csswphead;
|
|
var $cssnotheme;
|
|
var $reorder;
|
|
|
|
// header settings
|
|
var $child_name; // child theme name
|
|
var $child_author; // child theme author
|
|
var $child_authoruri; // child theme author website
|
|
var $child_themeuri; // child theme website
|
|
var $child_descr; // child theme description
|
|
var $child_tags; // child theme tags
|
|
var $child_version; // stylesheet version
|
|
|
|
// miscellaneous properties
|
|
var $fsize; // used to check if styles changed since last update
|
|
var $converted; // @imports coverted to <link>?
|
|
var $version; // version of last saved data
|
|
var $templates; // cache of parent template files
|
|
var $imports; // @import rules
|
|
var $recent; // history of edited styles
|
|
var $max_sel;
|
|
var $memory;
|
|
var $styles; // temporary update cache
|
|
var $temparray;
|
|
var $instances = array();
|
|
var $vendorrule = array(
|
|
'box\-sizing',
|
|
'font\-smoothing',
|
|
'border(\-(top|right|bottom|left))*\-radius',
|
|
'box\-shadow',
|
|
'transition',
|
|
'transition\-property',
|
|
'transition\-duration',
|
|
'transition\-timing\-function',
|
|
'transition\-delay',
|
|
'hyphens',
|
|
'transform',
|
|
'columns',
|
|
'column\-gap',
|
|
'column\-count',
|
|
);
|
|
var $configvars = array(
|
|
'addl_css',
|
|
'forcedep',
|
|
'swappath',
|
|
'cssunreg',
|
|
'csswphead',
|
|
'cssnotheme',
|
|
'reorder',
|
|
'parnt_deps',
|
|
'child_deps',
|
|
'hasstyles',
|
|
'parntloaded',
|
|
'childloaded',
|
|
'ignoreparnt',
|
|
'qpriority',
|
|
'mpriority',
|
|
'enqueue',
|
|
'handling',
|
|
'templates',
|
|
'max_sel',
|
|
'imports',
|
|
'child_version',
|
|
'child_author',
|
|
'child_name',
|
|
'child_themeuri',
|
|
'child_authoruri',
|
|
'child_descr',
|
|
'child_tags',
|
|
'parnt',
|
|
'child',
|
|
'configtype', // legacy support
|
|
'valkey',
|
|
'rulekey',
|
|
'qskey',
|
|
'selkey',
|
|
'querykey',
|
|
'tokenkey',
|
|
'recent',
|
|
'converted',
|
|
'fsize',
|
|
'version',
|
|
);
|
|
var $dicts = array(
|
|
'dict_qs' => 0,
|
|
'dict_sel' => 0,
|
|
'dict_query' => 0,
|
|
'dict_rule' => 0,
|
|
'dict_val' => 0,
|
|
'dict_seq' => 0,
|
|
'dict_token' => 0,
|
|
'val_ndx' => 0,
|
|
);
|
|
var $packer; // packer object
|
|
|
|
function __construct() {
|
|
$this->mem_chk();
|
|
// scalars
|
|
$this->querykey = 0;
|
|
$this->selkey = 0;
|
|
$this->qskey = 0;
|
|
$this->rulekey = 0;
|
|
$this->valkey = 0;
|
|
$this->max_sel = 0;
|
|
|
|
$this->child_name = '';
|
|
$this->child_author = 'Child Theme Configurator';
|
|
$this->child_themeuri = '';
|
|
$this->child_authoruri = '';
|
|
$this->child_descr = '';
|
|
$this->child_tags = '';
|
|
$this->child_version = '1.0';
|
|
|
|
$this->configtype = 'theme'; // legacy support
|
|
$this->child = '';
|
|
$this->parnt = '';
|
|
$this->ignoreparnt = 0;
|
|
$this->qpriority = 10;
|
|
$this->mpriority = 10;
|
|
$this->version = '2.5.6';
|
|
|
|
// do not set enqueue, not being set is used to flag old versions
|
|
|
|
// multi-dim arrays
|
|
$this->templates = array();
|
|
$this->imports = array( 'child' => array(), 'parnt' => array() );
|
|
|
|
$this->recent = array();
|
|
$this->packer = new ChildThemeConfiguratorPacker();
|
|
}
|
|
|
|
// helper function to globalize ctc object
|
|
function ctc() {
|
|
return ChildThemeConfigurator::ctc();
|
|
}
|
|
|
|
function mem_chk() {
|
|
$currmemory = $this->memory;
|
|
if ( function_exists( 'memory_get_peak_usage' ) ) {
|
|
$usage = memory_get_peak_usage();
|
|
} else {
|
|
$usage = memory_get_usage();
|
|
}
|
|
$this->memory = $usage;
|
|
$usage -= $currmemory;
|
|
return number_format( $this->memory / ( 1024 * 1024 ), 2 ) . ' MB diff ' . number_format( $usage / 1024, 2 ) . ' kB';
|
|
}
|
|
|
|
// loads current ctc config data
|
|
function load_config( $key = NULL ) {
|
|
//list(, $callerarr) = debug_backtrace(false);
|
|
//$caller = ( isset( $callerarr[ 'class' ] ) ? $callerarr[ 'class' ] . '::' : '' ) . $callerarr[ 'function' ];
|
|
if ( $key && isset( $this->dicts[ $key ] ) ):
|
|
$option = CHLD_THM_CFG_OPTIONS . apply_filters( 'chld_thm_cfg_option', '' );
|
|
if ( !$this->dicts[ $key ] ): // dict not loaded yet
|
|
//$this->ctc()->debug( 'memory before load: ' . $this->mem_chk(), __FUNCTION__, __CLASS__ );
|
|
//$this->ctc()->debug( $option . '_' . $key . ' -- called from ' . $caller, __FUNCTION__, __CLASS__ );
|
|
//
|
|
if ( !$this->ctc()->is_new && ( $config = get_site_option( $option . '_' . $key ) ) ):
|
|
$this->{ $key } = $config; // if not new child theme and option exists, load it
|
|
else:
|
|
$this->{ $key } = array(); // otherwise load empty array
|
|
endif;
|
|
|
|
$this->dicts[ $key ] = 1; // flag as loaded
|
|
//$this->ctc()->debug( 'memory after load: ' . $this->mem_chk(), __FUNCTION__, __CLASS__ );
|
|
else:
|
|
//$this->ctc()->debug( $key . ' already loaded -- called from ' . $caller, __FUNCTION__, __CLASS__ );
|
|
endif;
|
|
endif;
|
|
// initial setup
|
|
if ( empty( $key ) && !isset( $this->enqueue ) ):
|
|
$option = CHLD_THM_CFG_OPTIONS . apply_filters( 'chld_thm_cfg_option', '' );
|
|
//$this->ctc()->debug( 'loading option: ' . $option . '_' . ( $key ? $key : 'configvars' )
|
|
// . ' -- called from ' . $caller, __FUNCTION__, __CLASS__ );
|
|
if ( ( $configarray = get_site_option( $option . '_configvars' ) ) && count( $configarray ) ):
|
|
foreach ( $this->configvars as $configkey ):
|
|
if ( isset( $configarray[ $configkey ] ) )
|
|
$this->{$configkey} = $configarray[ $configkey ];
|
|
endforeach;
|
|
// convert dictionaries from < 2.1.0
|
|
if ( empty( $configarray[ 'version' ] ) || version_compare( $configarray[ 'version' ], '2.1.0', '<' ) ):
|
|
$this->convert_dict_arrays();
|
|
else:
|
|
$this->ctc()->debug( 'dict format up to date', __FUNCTION__, __CLASS__ );
|
|
endif;
|
|
else:
|
|
return FALSE;
|
|
endif;
|
|
endif;
|
|
|
|
}
|
|
|
|
// writes ctc config data to options api
|
|
function save_config( $override = NULL ) {
|
|
// set latest stylesheet size
|
|
$this->get_stylesheet_path();
|
|
global $wpdb;
|
|
if ( isset( $override ) ) $option = $override;
|
|
else $option = apply_filters( 'chld_thm_cfg_option', '' );
|
|
$option = CHLD_THM_CFG_OPTIONS . $option;
|
|
$configarray = array();
|
|
foreach ( $this->configvars as $configkey )
|
|
$configarray[ $configkey ] = empty( $this->{$configkey} ) ? NULL : $this->{ $configkey };
|
|
//$this->ctc()->debug( 'saving option: ' . $option . '_configvars', __FUNCTION__, __CLASS__ );
|
|
if ( is_multisite() ):
|
|
update_site_option( $option . '_configvars', $configarray );
|
|
else:
|
|
// do not autoload ( passing false above only works if value changes
|
|
update_option( $option . '_configvars', $configarray, FALSE );
|
|
endif;
|
|
|
|
foreach ( $this->dicts as $configkey => $loaded ):
|
|
if ( $loaded ): // only save if dict is loaded
|
|
//$this->ctc()->debug( 'saving option: ' . $option . '_' . $configkey, __FUNCTION__, __CLASS__ );
|
|
if ( is_multisite() ):
|
|
update_site_option( $option . '_' . $configkey, $this->{$configkey} );
|
|
else:
|
|
// do not autoload ( passing false for update_site_option only works if value changes )
|
|
update_option( $option . '_' . $configkey, $this->{$configkey}, FALSE );
|
|
endif;
|
|
endif;
|
|
endforeach;
|
|
|
|
}
|
|
|
|
/**
|
|
* determine effective stylesheet path and measure size
|
|
*/
|
|
function get_stylesheet_path() {
|
|
$stylesheet = apply_filters( 'chld_thm_cfg_target', $this->get_child_target( $this->ctc()->get_child_stylesheet() ), $this );
|
|
$this->fsize = filesize( $stylesheet );
|
|
$this->ctc()->debug( 'updated file size: ' . $this->fsize, __FUNCTION__, __CLASS__ );
|
|
return $stylesheet;
|
|
}
|
|
/**
|
|
* get_prop
|
|
* Getter interface (data sliced different ways depending on objname )
|
|
*/
|
|
function get_prop( $property, $params = NULL ) {
|
|
switch ( $property ):
|
|
case 'fsize':
|
|
return empty( $this->fsize ) ? FALSE : $this->fsize;
|
|
case 'converted':
|
|
return !empty( $this->converted );
|
|
case 'max_sel':
|
|
return empty( $this->max_sel ) ? FALSE : $this->max_sel;
|
|
case 'imports':
|
|
|
|
return $this->obj_to_utf8( !empty( $this->imports[ 'child' ] ) && is_array( $this->imports[ 'child' ] ) ?
|
|
( current( $this->imports[ 'child' ] ) == 1 ?
|
|
array_keys( $this->imports[ 'child' ] ) :
|
|
array_keys( array_flip( $this->imports[ 'child' ] ) ) ) :
|
|
array() );
|
|
case 'queries':
|
|
return $this->obj_to_utf8( $this->denorm_dict_qs() );
|
|
case 'selectors':
|
|
return empty( $params[ 'key' ] ) ?
|
|
array() : $this->obj_to_utf8( $this->denorm_dict_qs( $params[ 'key' ] ) );
|
|
case 'rule_val':
|
|
return empty( $params[ 'key' ] ) ? array() : $this->denorm_rule_val( $params[ 'key' ] );
|
|
case 'val_qry':
|
|
if ( isset( $params[ 'rule' ] ) ):
|
|
return empty( $params[ 'key' ] ) ?
|
|
array() : $this->denorm_val_query( $params[ 'key' ], $params[ 'rule' ] );
|
|
endif;
|
|
case 'qsid':
|
|
return empty( $params[ 'key' ] ) ?
|
|
array() : $this->obj_to_utf8( $this->denorm_sel_val( $params[ 'key' ] ) );
|
|
case 'rules':
|
|
$this->load_config( 'dict_rule' );
|
|
asort( $this->dict_rule );
|
|
//ksort( $this->dict_rule );
|
|
//return $this->obj_to_utf8( $this->dict_rule );
|
|
/** lookup ** -- need to flip array??? */
|
|
return $this->obj_to_utf8( array_flip( $this->dict_rule ) );
|
|
case 'child':
|
|
return $this->child;
|
|
case 'parnt':
|
|
return $this->parnt;
|
|
case 'configtype': // legacy plugin extension support
|
|
return $this->configtype;
|
|
case 'enqueue':
|
|
return empty( $this->enqueue ) ? FALSE : $this->enqueue;
|
|
case 'addl_css':
|
|
return empty( $this->addl_css ) ? array() : $this->addl_css;
|
|
case 'parnt_imp':
|
|
return empty( $this->parnt_imp ) ? array() : $this->parnt_imp;
|
|
case 'forcedep': // v2.1.3
|
|
return empty( $this->forcedep ) ? array() : array_keys( $this->forcedep );
|
|
case 'swappath': // v2.3.1
|
|
return empty( $this->swappath ) ? array() : (array) $this->swappath;
|
|
case 'parnt_deps':
|
|
return empty( $this->parnt_deps ) ? array() : $this->quotify_dependencies( 'parnt_deps' );
|
|
case 'child_deps':
|
|
return empty( $this->child_deps ) ? array() : $this->quotify_dependencies( 'child_deps' );
|
|
case 'templates':
|
|
return empty( $this->templates ) ? FALSE : $this->templates;
|
|
case 'ignoreparnt':
|
|
return empty( $this->ignoreparnt ) ? 0 : 1;
|
|
case 'qpriority':
|
|
return empty( $this->qpriority ) ? 10 : $this->qpriority;
|
|
case 'mpriority':
|
|
return empty( $this->mpriority ) ? 10 : $this->mpriority;
|
|
case 'parntloaded':
|
|
return empty( $this->parntloaded ) ? FALSE : $this->parntloaded;
|
|
case 'childloaded':
|
|
return empty( $this->childloaded ) ? FALSE : $this->childloaded;
|
|
case 'hasstyles':
|
|
return empty( $this->hasstyles ) ? 0 : 1;
|
|
case 'cssunreg':
|
|
return empty( $this->cssunreg ) ? 0 : 1;
|
|
case 'csswphead':
|
|
return empty( $this->csswphead ) ? 0 : 1;
|
|
case 'cssnotheme':
|
|
return empty( $this->cssnotheme ) ? 0 : 1;
|
|
case 'reorder':
|
|
return empty( $this->reorder ) ? 0 : 1;
|
|
case 'handling':
|
|
return empty( $this->handling ) ? 'primary' : $this->handling;
|
|
case 'child_name':
|
|
return stripslashes( $this->child_name );
|
|
case 'author':
|
|
return stripslashes( $this->child_author );
|
|
case 'themeuri':
|
|
return isset( $this->child_themeuri ) ? $this->child_themeuri : FALSE;
|
|
case 'authoruri':
|
|
return isset( $this->child_authoruri ) ? $this->child_authoruri : FALSE;
|
|
case 'descr':
|
|
return isset( $this->child_descr ) ? stripslashes( $this->child_descr ) : FALSE;
|
|
case 'tags':
|
|
return isset( $this->child_tags ) ? stripslashes( $this->child_tags ) : FALSE;
|
|
case 'version':
|
|
return $this->child_version;
|
|
case 'preview':
|
|
$this->styles = '';
|
|
if ( empty( $params[ 'key' ] ) || 'child' == $params[ 'key' ] ):
|
|
$this->read_stylesheet( 'child', $this->ctc()->get_child_stylesheet() );
|
|
else:
|
|
if ( isset( $this->addl_css ) ):
|
|
foreach ( $this->addl_css as $file ):
|
|
$this->styles .= '/*** BEGIN ' . $file . ' ***/' . LF;
|
|
$this->read_stylesheet( 'parnt', $file );
|
|
$this->styles .= '/*** END ' . $file . ' ***/' . LF;
|
|
endforeach;
|
|
endif;
|
|
if ( $this->get_prop( 'hasstyles' ) && !$this->get_prop( 'ignoreparnt' ) ):
|
|
$this->styles .= '/*** BEGIN Parent style.css ***/' . LF;
|
|
$this->read_stylesheet( 'parnt', 'style.css' );
|
|
$this->styles .= '/*** END Parent style.css ***/' . LF;
|
|
endif;
|
|
if ( 'separate' == $this->get_prop( 'handling' ) ):
|
|
$this->styles .= '/*** BEGIN Child style.css ***/' . LF;
|
|
$this->read_stylesheet( 'child', 'style.css' );
|
|
$this->styles .= '/*** END Child style.css ***/' . LF;
|
|
endif;
|
|
endif;
|
|
$this->normalize_css();
|
|
return $this->styles;
|
|
break;
|
|
default:
|
|
return $this->obj_to_utf8( apply_filters( 'chld_thm_get_prop', NULL, $property, $params ) );
|
|
endswitch;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* set_prop
|
|
* Setter interface (scalar values only)
|
|
*/
|
|
function set_prop( $property, $value ) {
|
|
if ( is_null( $this->{ $property } ) || is_scalar( $this->{ $property } ) )
|
|
$this->{ $property } = $value;
|
|
else return FALSE;
|
|
}
|
|
|
|
// formats css string for accurate parsing
|
|
function normalize_css() {
|
|
if ( preg_match( "/(\}[\w\#\.]|; *\})/", $this->styles ) ): // prettify compressed CSS
|
|
$this->styles = preg_replace( "/\*\/\s*/s", "*/\n", $this->styles ); // end comment
|
|
$this->styles = preg_replace( "/\{\s*/s", " {\n ", $this->styles ); // open brace
|
|
$this->styles = preg_replace( "/;\s*/s", ";\n ", $this->styles ); // semicolon
|
|
$this->styles = preg_replace( "/\s*\}\s*/s", "\n}\n", $this->styles ); // close brace
|
|
endif;
|
|
}
|
|
|
|
function quotify_dependencies( $prop ) {
|
|
$arr = array();
|
|
foreach ( array_diff( $this->{ $prop }, $this->get_prop( 'forcedep' ) ) as $el )
|
|
$arr[] = "'" . str_replace("'", "\'", $el ) . "'";
|
|
return $arr;
|
|
}
|
|
|
|
// creates header comments for stylesheet
|
|
function get_css_header() {
|
|
return array(
|
|
'Theme Name' => $this->get_prop( 'child_name' ),
|
|
'Theme URI' => ( ( $attr = $this->get_prop( 'themeuri' ) ) ? $attr : '' ),
|
|
'Template' => $this->get_prop( 'parnt' ),
|
|
'Author' => $this->get_prop( 'author' ),
|
|
'Author URI' => ( ( $attr = $this->get_prop( 'authoruri' ) ) ? $attr : '' ),
|
|
'Description' => ( ( $attr = $this->get_prop( 'descr' ) ) ? $attr : '' ),
|
|
'Tags' => ( ( $attr = $this->get_prop( 'tags' ) ) ? $attr : '' ),
|
|
'Version' => $this->get_prop( 'version' ) . '.' . time(),
|
|
'Updated' => current_time( 'mysql' ),
|
|
);
|
|
}
|
|
|
|
function get_css_header_comment( $handling = 'primary' ) {
|
|
if ( 'separate' == $handling ):
|
|
$contents = "/*" . LF
|
|
. 'CTC Separate Stylesheet' . LF
|
|
. 'Updated: ' . current_time( 'mysql' ) . LF
|
|
. '*/' . LF;
|
|
else:
|
|
$contents = "/*" . LF;
|
|
foreach ( $this->get_css_header() as $param => $value ):
|
|
if ( $value ):
|
|
$contents .= $param . ': ' . $value . LF;
|
|
endif;
|
|
endforeach;
|
|
$contents .= LF . "*/" . LF . $this->get_css_imports();
|
|
|
|
endif;
|
|
return $contents;
|
|
}
|
|
|
|
function get_css_imports() {
|
|
$newheader = '';
|
|
if ( 'import' == $this->get_prop( 'enqueue' ) ):
|
|
$this->ctc()->debug( 'using import ', __FUNCTION__, __CLASS__ );
|
|
if ( ! $this->get_prop( 'ignoreparnt' ) )
|
|
$newheader .= "@import url('../" . $this->get_prop( 'parnt' ) . "/style.css');" . LF;
|
|
endif;
|
|
return $newheader;
|
|
}
|
|
|
|
// formats file path for child theme file
|
|
function get_child_target( $file = '', $theme = NULL ) {
|
|
return trailingslashit( get_theme_root() ) . trailingslashit( $theme ? $theme : $this->get_prop( 'child' ) ) . $file;
|
|
}
|
|
|
|
// formats file path for parent theme file
|
|
function get_parent_source( $file = 'style.css', $theme = NULL ) {
|
|
return trailingslashit( get_theme_root() ) . trailingslashit( $theme ? $theme : $this->get_prop( 'parnt' ) ) . $file;
|
|
}
|
|
|
|
/**
|
|
* get_dict_id
|
|
* lookup function retrieves normalized id from string input
|
|
* automatically adds to dictionary if it does not exist
|
|
* incrementing key value for dictionary
|
|
*/
|
|
function get_dict_id( $dict, $value ) {
|
|
if ( FALSE === ( $id = $this->lookup_dict_value( $dict, $value ) ) ):
|
|
// add value to dictionary
|
|
$id = ++$this->{ $dict . 'key' };
|
|
$this->set_dict_value( $dict, $value, $id );
|
|
endif;
|
|
return $id;
|
|
}
|
|
|
|
function lookup_dict_value( $dict, $value ){
|
|
//$this->ctc()->debug( 'dict: ' . $dict . ' value: %' . $value . '%', __FUNCTION__, __CLASS__ );
|
|
$property = 'dict_' . $dict;
|
|
if ( $id = array_search( (string) $value, $this->{ $property } ) )
|
|
return $id;
|
|
return FALSE;
|
|
}
|
|
|
|
function get_dict_value( $dict, $id ) {
|
|
$property = 'dict_' . $dict;
|
|
return ( isset( $this->{ $property }[ $id ] ) )
|
|
? $this->{ $property }[ $id ]
|
|
: FALSE;
|
|
}
|
|
|
|
function set_dict_value( $dict, $value, $id ) {
|
|
$property = 'dict_' . $dict;
|
|
$this->{ $property }[ $id ] = ( string ) $value;
|
|
}
|
|
|
|
/**
|
|
* get_qsid
|
|
* query/selector id is the combination of two dictionary values
|
|
* also throttles parsing if memory limit is reached
|
|
*/
|
|
function get_qsid( $query, $sel ) {
|
|
$qs = $this->get_dict_id( 'query', $query ) . ':' . $this->get_dict_id( 'sel', $sel );
|
|
return $this->get_dict_id( 'qs', $qs );
|
|
}
|
|
|
|
function unpack_val_ndx( $qsid ){
|
|
if ( isset( $this->val_ndx[ $qsid ] ) ):
|
|
try {
|
|
$this->packer->reset( $this->packer->decode( $this->val_ndx[ $qsid ] ) );
|
|
return $this->packer->unpack();
|
|
} catch ( Exception $e ){
|
|
$this->ctc()->debug( 'Unpack failed -- ' . $e->getMessage(), __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
}
|
|
endif;
|
|
return FALSE;
|
|
}
|
|
|
|
function pack_val_ndx( $qsid, $valarr ){
|
|
try {
|
|
$this->val_ndx[ $qsid ] = $this->packer->encode( $this->packer->pack( $valarr ) );
|
|
} catch ( Exception $e ){
|
|
$this->ctc()->debug( 'Pack failed -- ' . $e->getMessage(), __FUNCTION__, __CLASS__ );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* update_arrays
|
|
* accepts CSS properties as raw strings and normilizes into
|
|
* CTC object arrays, creating update cache in the process.
|
|
* ( Update cache is returned to UI via AJAX to refresh page )
|
|
* This has been refactored in v1.7.5 to accommodate multiple values per property.
|
|
* @param $template p or c
|
|
* @param $query media query
|
|
* @param $sel selector
|
|
* @param $rule property (rule)
|
|
* @param $value individual value ( property has array of values )
|
|
* @param $important important flag for value
|
|
* @param $rulevalid unique id of value for property
|
|
* @param $reset clear current values to prevent multiple values from being generated from Raw CSS post input data
|
|
* @return $qsid query/selector id for this entry
|
|
*/
|
|
function update_arrays(
|
|
$template,
|
|
$query,
|
|
$sel,
|
|
$rule = NULL,
|
|
$value = NULL,
|
|
$important = 0,
|
|
$rulevalid = NULL,
|
|
$reset = FALSE
|
|
) {
|
|
// if ( $this->max_sel ) return; // Future use
|
|
if ( FALSE === strpos( $query, '@' ) )
|
|
$query = 'base';
|
|
// normalize selector styling
|
|
$sel = implode( ', ', preg_split( '#\s*,\s*#s', trim( $sel ) ) );
|
|
$qsid = $this->get_qsid( $query, $this->tokenize( $sel ) );
|
|
// set data and value
|
|
if ( $rule ):
|
|
// get ids and quit if max is reached ( get_qsid handles )
|
|
$ruleid = $this->get_dict_id( 'rule', $rule );
|
|
$valid = $this->get_dict_id( 'val', $value );
|
|
/**
|
|
* v2.1.0
|
|
* pack/unpack val_ndx
|
|
*/
|
|
// create empty array IF value array does not exist
|
|
if ( FALSE === ( $valarr = $this->unpack_val_ndx( $qsid ) ) )
|
|
$valarr = array(
|
|
$ruleid => array(
|
|
$template => array(),
|
|
),
|
|
);
|
|
// create empty array IF rule array does not exist
|
|
if ( !isset( $valarr[ $ruleid ] ) )
|
|
$valarr[ $ruleid ] = array(
|
|
$template => array(),
|
|
);
|
|
// create empty rule array if template is child and reset is TRUE
|
|
// or IF template array does not exist
|
|
if ( ( $reset && 'child' == $template ) || !isset( $valarr[ $ruleid ][ $template ] ) )
|
|
$valarr[ $ruleid ][ $template ] = array();
|
|
|
|
// rulevalid passed
|
|
//$this->ctc()->debug( 'rule: ' . $rule . ' ' . $ruleid . ' value: ' . ( '' == $value? 'NULL' : '%' . $value . '%' ) . ' ' . ( FALSE == $valid ? 'FALSE' : $valid ) . ' valarr: ' . print_r( $valarr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
if ( isset( $rulevalid ) ):
|
|
$this->unset_rule_value( $valarr[ $ruleid ][ $template ], $rulevalid );
|
|
// value empty?
|
|
if ( '' === $value ):
|
|
// value exist?
|
|
elseif ( $id = $this->rule_value_exists( $valarr[ $ruleid ][ $template ], $valid ) ):
|
|
$this->unset_rule_value( $valarr[ $ruleid ][ $template ], $id );
|
|
$this->update_rule_value( $valarr[ $ruleid ][ $template ], $rulevalid, $valid, $important );
|
|
// update new value
|
|
else:
|
|
$this->update_rule_value( $valarr[ $ruleid ][ $template ], $rulevalid, $valid, $important );
|
|
endif;
|
|
// rulevalid not passed
|
|
else:
|
|
// value exist?
|
|
if ( $id = $this->rule_value_exists( $valarr[ $ruleid ][ $template ], $valid ) ):
|
|
$this->unset_rule_value( $valarr[ $ruleid ][ $template ], $id );
|
|
$this->update_rule_value( $valarr[ $ruleid ][ $template ], $id, $valid, $important );
|
|
// get new id and update new value
|
|
else:
|
|
$id = $this->get_rule_value_id( $valarr[ $ruleid ][ $template ] );
|
|
$this->update_rule_value( $valarr[ $ruleid ][ $template ], $id, $valid, $important );
|
|
endif;
|
|
endif;
|
|
|
|
// moved call to prune_if_empty to parse_post_data v2.2.5
|
|
|
|
$this->pack_val_ndx( $qsid, $valarr );
|
|
// return query selector id
|
|
return $qsid;
|
|
endif;
|
|
}
|
|
|
|
/**
|
|
* rule_value_exists
|
|
* Determine if a value already exists for a property
|
|
* and return its id
|
|
*/
|
|
function rule_value_exists( &$arr, $valid ) {
|
|
foreach ( $arr as $valarr ):
|
|
if ( isset( $valarr[ 0 ] ) && isset( $valarr[ 2 ] ) && $valid == $valarr[ 0 ] ):
|
|
return $valarr[ 2 ];
|
|
endif;
|
|
endforeach;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* get_rule_value_id
|
|
* Generate a new rulevalid by iterating existing ids
|
|
* and returning the next in sequence
|
|
*/
|
|
function get_rule_value_id( &$arr ) {
|
|
$newid = 1;
|
|
foreach ( $arr as $valarr )
|
|
if ( isset( $valarr[ 2 ] ) && $valarr[ 2 ] >= $newid ) $newid = $valarr[ 2 ] + 1;
|
|
return $newid;
|
|
}
|
|
|
|
/**
|
|
* update_rule_value
|
|
* Generate a new value subarray
|
|
*/
|
|
function update_rule_value( &$arr, $id, $valid, $important ) {
|
|
$arr[] = array(
|
|
$valid,
|
|
$important,
|
|
$id,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* unset_rule_value
|
|
* Delete (splice) old value subarray from values
|
|
*/
|
|
function unset_rule_value( &$arr, $id ) {
|
|
$index = 0;
|
|
foreach ( $arr as $valarr ):
|
|
if ( $id == $valarr[ 2 ] ):
|
|
array_splice( $arr, $index, 1 );
|
|
break;
|
|
endif;
|
|
++$index;
|
|
endforeach;
|
|
}
|
|
|
|
/**
|
|
* prune_if_empty
|
|
* Automatically cleans up hierarchies when no values exist
|
|
* in either parent or child for a given selector.
|
|
*/
|
|
function prune_if_empty( $qsid ) {
|
|
$empty = $this->get_dict_id( 'val', '' );
|
|
if ( FALSE == ( $valarr = $this->unpack_val_ndx( $qsid ) ) ) return FALSE;
|
|
foreach ( $valarr as $ruleid => $arr ):
|
|
foreach ( array( 'c', 'p' ) as $template ):
|
|
if ( isset( $arr[ $template ] ) ):
|
|
// v1.7.5: don't prune until converted to multi value format
|
|
if ( !is_array( $arr[ $template ] ) ) return FALSE;
|
|
// otherwise check each value, if not empty return false
|
|
foreach ( $arr[ $template ] as $valarr )
|
|
if ( $empty != $valarr[ 0 ] ) return FALSE;
|
|
endif;
|
|
endforeach;
|
|
endforeach;
|
|
// no values, prune from sel index, val index and qs dict data ( keep other dictionary records )
|
|
unset( $this->val_ndx[ $qsid ] );
|
|
unset( $this->dict_qs[ $qsid ] );
|
|
unset( $this->dict_seq[ $qsid ] );
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* recurse_directory
|
|
* searches filesystem for valid files based on parameters and returns array of filepaths.
|
|
* Core WP recurse function is not used because we require logic specific to CTC.
|
|
*/
|
|
function recurse_directory( $rootdir, $ext = 'css', $all = FALSE ) {
|
|
// make sure we are only recursing theme and plugin files
|
|
if ( !$this->is_file_ok( $rootdir, 'search' ) )
|
|
return array();
|
|
$files = array();
|
|
$dirs = array( $rootdir );
|
|
$loops = 0;
|
|
if ( 'img' == $ext )
|
|
$ext = '(' . implode( '|', array_keys( $this->ctc()->imgmimes ) ) . ')';
|
|
while( count( $dirs ) && $loops < CHLD_THM_CFG_MAX_RECURSE_LOOPS ): // failsafe valve
|
|
$loops++;
|
|
$dir = array_shift( $dirs );
|
|
if ( $handle = opendir( $dir ) ):
|
|
while ( FALSE !== ( $file = readdir( $handle ) ) ):
|
|
if ( preg_match( "/^\./", $file ) ) continue;
|
|
$filepath = trailingslashit( $dir ) . $file;
|
|
if ( is_dir( $filepath ) ):
|
|
array_unshift( $dirs, $filepath );
|
|
if ( $all ):
|
|
$files[] = $filepath;
|
|
endif;
|
|
elseif ( is_file( $filepath ) && ( $all || preg_match( "/\.".$ext."$/i", $filepath ) ) ):
|
|
$files[] = $filepath;
|
|
endif;
|
|
endwhile;
|
|
closedir( $handle );
|
|
endif;
|
|
endwhile;
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* parse_post_data
|
|
* Parse user form input into separate properties and pass to update_arrays
|
|
* FIXME - this function has grown too monolithic - refactor and componentize
|
|
*/
|
|
function parse_post_data() {
|
|
$this->load_config( 'dict_query' );
|
|
$this->load_config( 'dict_sel' );
|
|
$this->load_config( 'dict_token' );
|
|
$this->load_config( 'dict_rule' );
|
|
$this->load_config( 'dict_val' );
|
|
$this->load_config( 'val_ndx' );
|
|
$this->load_config( 'dict_seq' );
|
|
$this->load_config( 'dict_qs' );
|
|
$this->cache_updates = TRUE;
|
|
// process RAW CSS input
|
|
if ( isset( $_POST[ 'ctc_new_selectors' ] ) ):
|
|
$this->styles = $this->parse_css_input( LF . $_POST[ 'ctc_new_selectors' ] );
|
|
$this->parse_css( 'child',
|
|
isset( $_POST[ 'ctc_sel_ovrd_query' ] ) ? trim( $_POST[ 'ctc_sel_ovrd_query' ] ) : NULL,
|
|
FALSE,
|
|
'',
|
|
TRUE
|
|
);
|
|
// process WEB FONTS & CSS inputs
|
|
elseif ( isset( $_POST[ 'ctc_child_imports' ] ) ):
|
|
$this->imports[ 'child' ] = array();
|
|
$this->styles = $this->parse_css_input( $_POST[ 'ctc_child_imports' ] );
|
|
$this->parse_css( 'child' );
|
|
// process CONFIGURE inputs
|
|
elseif ( isset( $_POST[ 'ctc_configtype' ] ) ):
|
|
ob_start();
|
|
do_action( 'chld_thm_cfg_get_stylesheets' );
|
|
$this->ctc()->updates[] = array(
|
|
'obj' => 'stylesheets',
|
|
'key' => '',
|
|
'data' => ob_get_contents(),
|
|
);
|
|
ob_end_clean();
|
|
ob_start();
|
|
do_action( 'chld_thm_cfg_get_backups' );
|
|
$this->ctc()->updates[] = array(
|
|
'obj' => 'backups',
|
|
'key' => '',
|
|
'data' => ob_get_contents(),
|
|
);
|
|
ob_end_clean();
|
|
return;
|
|
// process SAVE inputs
|
|
else:
|
|
// New query added v2.3.0
|
|
$newquery = isset( $_POST[ 'ctc_rewrite_query' ] ) ?
|
|
$this->sanitize( $this->parse_css_input( $_POST[ 'ctc_rewrite_query' ] ) ) : NULL;
|
|
$newselector = isset( $_POST[ 'ctc_rewrite_selector' ] ) ?
|
|
$this->sanitize( $this->parse_css_input( $_POST[ 'ctc_rewrite_selector' ] ) ) : NULL;
|
|
$newqsid = NULL;
|
|
|
|
// set the custom sequence value
|
|
foreach ( preg_grep( '#^ctc_ovrd_child_seq_#', array_keys( $_POST ) ) as $post_key ):
|
|
if ( preg_match( '#^ctc_ovrd_child_seq_(\d+)$#', $post_key, $matches ) ):
|
|
$qsid = $matches[ 1 ];
|
|
$seq = intval( $_POST[ $post_key ] );
|
|
$this->ctc()->debug( 'set seq( ' . $qsid . ' ): custom: ' . $seq, __FUNCTION__, __CLASS__ );
|
|
if ( $seq != $qsid ):
|
|
$this->set_dict_value( 'seq', $seq, $qsid );
|
|
else:
|
|
unset( $this->dict_seq[ $seq ] );
|
|
endif;
|
|
endif;
|
|
endforeach;
|
|
|
|
// iterate each property input
|
|
$parts = array();
|
|
foreach ( preg_grep( '#^ctc_(ovrd|\d+)_child#', array_keys( $_POST ) ) as $post_key ):
|
|
|
|
// parse input key into individual components if it matches specific format, skip otherwise
|
|
if ( preg_match( '#^ctc_(ovrd|\d+)_child_([\w\-]+?)_(\d+?)_(\d+?)(_(.+))?$#', $post_key, $matches ) ):
|
|
$valid = $matches[ 1 ]; // this is used for inputs from property value tab
|
|
$rule = $matches[ 2 ]; // property name
|
|
if ( NULL == $rule || FALSE === $this->lookup_dict_value( 'rule', $rule ) )
|
|
continue;
|
|
$qsid = $matches[ 3 ]; // query/selector id
|
|
$rulevalid = $matches[ 4 ]; // id to identify multiple values of same property
|
|
// normalize input value
|
|
$value = $this->normalize_color( $this->sanitize( $this->parse_css_input( $_POST[ $post_key ] ) ) );
|
|
// set important flag
|
|
$important = $this->is_important( $value ); // strip and set if !important passed in input
|
|
// set important if checkbox input is set
|
|
if ( !empty( $_POST[ 'ctc_' . $valid . '_child_' . $rule . '_i_' . $qsid . '_' . $rulevalid ] ) ) $important = 1;
|
|
|
|
// get current values from query/selector id if it exists, skip this property otherwise
|
|
$selarr = $this->denorm_query_sel( $qsid );
|
|
if ( empty( $selarr ) ) continue;
|
|
|
|
// if there is a "rule-part" (e.g., border or gradient properties ), store in parts array and process separately.
|
|
if ( !empty( $matches[ 6 ] ) ):
|
|
$parts[ $qsid ][ $rule ][ 'values' ][ $rulevalid ][ $matches[ 6 ] ] = $value;
|
|
$parts[ $qsid ][ $rule ][ 'values' ][ $rulevalid ][ 'important' ] = $important;
|
|
$parts[ $qsid ][ $rule ][ 'query' ] = $selarr[ 'query' ];
|
|
$parts[ $qsid ][ $rule ][ 'selector' ] = $selarr[ 'selector' ];
|
|
// otherwise process this property
|
|
else:
|
|
$newqsid = $this->update_property(
|
|
$newquery,
|
|
$newselector,
|
|
$selarr[ 'query' ],
|
|
$selarr[ 'selector' ],
|
|
$rule,
|
|
$value,
|
|
$important,
|
|
$rulevalid
|
|
);
|
|
endif;
|
|
endif;
|
|
endforeach;
|
|
/**
|
|
* Inputs for border and background-image are broken into multiple "rule parts"
|
|
* With the addition of multiple property values in v1.7.5, the parts loop
|
|
* has been modified to segment the parts into rulevalids under a new 'values' array.
|
|
* The important flag has also been moved into the parts array.
|
|
*/
|
|
foreach ( $parts as $qsid => $rules ):
|
|
foreach ( $rules as $rule => $rule_arr ):
|
|
// new 'values' array to segment parts into rulevalids
|
|
foreach ( $rule_arr[ 'values' ] as $rulevalid => $rule_part ):
|
|
if ( 'background' == $rule ):
|
|
$value = $rule_part[ 'background_url' ];
|
|
elseif ( 'background-image' == $rule ):
|
|
if ( empty( $rule_part[ 'background_url' ] ) ):
|
|
// custom multi-stop or radial gradients can be passed verbatim in the origin field
|
|
// but will not be parsed for vender-prefix support.
|
|
if ( !empty( $rule_part[ 'background_origin' ] )
|
|
&& preg_match( '{gradient}', $rule_part[ 'background_origin' ] ) ):
|
|
$value = $rule_part[ 'background_origin' ];
|
|
|
|
elseif ( empty( $rule_part[ 'background_color2' ] ) ):
|
|
$value = '';
|
|
else:
|
|
if ( empty( $rule_part[ 'background_origin' ] ) )
|
|
$rule_part[ 'background_origin' ] = 'top';
|
|
if ( empty( $rule_part[ 'background_color1' ] ) )
|
|
$rule_part[ 'background_color1' ] = $rule_part[ 'background_color2' ];
|
|
$value = implode( ':', array(
|
|
$rule_part[ 'background_origin' ],
|
|
$rule_part[ 'background_color1' ], '0%',
|
|
$rule_part[ 'background_color2' ], '100%'
|
|
) );
|
|
endif;
|
|
else:
|
|
$value = $rule_part[ 'background_url' ];
|
|
endif;
|
|
elseif ( preg_match( '#^border(\-(top|right|bottom|left))?$#', $rule ) ):
|
|
if ( empty( $rule_part[ 'border_width' ] ) && !empty( $rule_part[ 'border_color' ] ) )
|
|
$rule_part[ 'border_width' ] = '1px';
|
|
if ( empty( $rule_part[ 'border_style' ] ) && !empty( $rule_part[ 'border_color' ] ) )
|
|
$rule_part[ 'border_style' ] = 'solid';
|
|
$value = implode( ' ', array(
|
|
$rule_part[ 'border_width' ],
|
|
$rule_part[ 'border_style' ],
|
|
$rule_part[ 'border_color' ]
|
|
) );
|
|
else:
|
|
$value = '';
|
|
endif;
|
|
|
|
$newqsid = $this->update_property(
|
|
$newquery,
|
|
$newselector,
|
|
$rule_arr[ 'query' ],
|
|
$rule_arr[ 'selector' ],
|
|
$rule,
|
|
$value,
|
|
$rule_part[ 'important' ],
|
|
$rulevalid
|
|
);
|
|
|
|
endforeach;
|
|
endforeach;
|
|
endforeach;
|
|
|
|
// remove if all values have been cleared - moved from update_arrays v2.2.5
|
|
$this->prune_if_empty( $qsid );
|
|
|
|
if ( $newqsid != $qsid )
|
|
$qsid = $newqsid;
|
|
|
|
// return updated qsid to browser to update form
|
|
if ( $this->ctc()->cache_updates )
|
|
$this->ctc()->updates[] = array(
|
|
'obj' => 'qsid',
|
|
'key' => $qsid,
|
|
'data' => $this->obj_to_utf8( $this->denorm_sel_val( $qsid ) ),
|
|
);
|
|
|
|
do_action( 'chld_thm_cfg_update_qsid', $qsid );
|
|
endif;
|
|
|
|
// update enqueue function if imports have not been converted or new imports passed
|
|
if ( isset( $_POST[ 'ctc_child_imports' ] ) || !$this->get_prop( 'converted' ) )
|
|
add_action( 'chld_thm_cfg_addl_files', array( $this->ctc(), 'enqueue_parent_css' ), 15, 2 );
|
|
}
|
|
|
|
/**
|
|
* standarized property update function
|
|
* added v2.3.0
|
|
*/
|
|
function update_property(
|
|
$newquery,
|
|
$newselector,
|
|
$query,
|
|
$selector,
|
|
$rule,
|
|
$value,
|
|
$important,
|
|
$rulevalid
|
|
){
|
|
// If this is a renamed selector, add new selector to data
|
|
// otherwise update existing selector
|
|
$newqsid = $this->update_arrays(
|
|
'c',
|
|
$newquery ? $newquery : $query,
|
|
$newselector ? $newselector : $selector,
|
|
$rule,
|
|
trim( $value ),
|
|
$important,
|
|
$rulevalid
|
|
);
|
|
// if query or selector have been renamed,
|
|
// clear the original selector's value:
|
|
if ( $newquery || $newselector ):
|
|
$qsid = $this->update_arrays(
|
|
'c',
|
|
$query,
|
|
$selector,
|
|
$rule,
|
|
'',
|
|
0,
|
|
$rulevalid
|
|
);
|
|
// add new sequence entry
|
|
$seq = $this->get_dict_value( 'seq', $qsid );
|
|
$this->set_dict_value( 'seq', $newqsid, $seq );
|
|
endif;
|
|
return $newqsid;
|
|
}
|
|
|
|
/**
|
|
* parse_css_input
|
|
* Normalize raw user CSS input so that the parser can read it.
|
|
*/
|
|
function parse_css_input( $styles ) {
|
|
return $this->repl_octal( stripslashes( $this->esc_octal( $styles ) ) );
|
|
}
|
|
|
|
// strips non printables and potential commands
|
|
function sanitize( $styles ) {
|
|
return sanitize_text_field( preg_replace( '/[^[:print:]]|[\{\}].*/', '', $styles ) );
|
|
}
|
|
|
|
// escapes octal values in input to allow for specific ascii strings in content rule
|
|
function esc_octal( $styles ){
|
|
return preg_replace( "#(['\"])\\\\([0-9a-f]{4})(['\"])#i", "$1##bs##$2$3", $styles );
|
|
}
|
|
|
|
// unescapes octal values for writing specific ascii strings in content rule
|
|
function repl_octal( $styles ) {
|
|
return str_replace( "##bs##", "\\", $styles );
|
|
}
|
|
|
|
/**
|
|
* parse_css_file
|
|
* reads stylesheet to get WordPress meta data and passes rest to parse_css
|
|
*/
|
|
function parse_css_file( $template, $file = 'style.css', $cfgtemplate = FALSE ) {
|
|
if ( '' == $file ) $file = 'style.css';
|
|
|
|
/**
|
|
* Future use: have we run out of memory?
|
|
*
|
|
if ( $this->max_sel ):
|
|
//$this->ctc()->debug( 'Insufficient memory to parse file.', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
endif;
|
|
*/
|
|
// turn off caching when parsing files to reduce memory usage
|
|
$this->ctc()->cache_updates = FALSE;
|
|
$this->styles = ''; // reset styles
|
|
$this->read_stylesheet( $template, $file );
|
|
// get theme name
|
|
$regex = '#Theme Name:\s*(.+?)\n#i';
|
|
preg_match( $regex, $this->styles, $matches );
|
|
$child_name = $this->get_prop( 'child_name' );
|
|
if ( !empty( $matches[ 1 ] ) && 'child' == $template && empty( $child_name ) ) $this->set_prop( 'child_name', $matches[ 1 ] );
|
|
$this->parse_css(
|
|
$cfgtemplate ? $cfgtemplate : $template,
|
|
NULL,
|
|
TRUE,
|
|
$this->ctc()->normalize_path( dirname( $file ) )
|
|
);
|
|
}
|
|
|
|
// loads raw css file into local memory
|
|
function read_stylesheet( $template = 'child', $file = 'style.css' ) {
|
|
|
|
// these conditions support revert/restore option in 1.6.0+
|
|
if ( 'all' == $file ) return;
|
|
elseif ( '' == $file ) $file = 'style.css';
|
|
// end revert/restore conditions
|
|
|
|
$source = $this->get_prop( $template );
|
|
if ( empty( $source ) || !is_scalar( $source ) ) return FALSE;
|
|
$themedir = trailingslashit( get_theme_root() ) . $source;
|
|
$stylesheet = apply_filters( 'chld_thm_cfg_' . $template, trailingslashit( $themedir )
|
|
. $file , ( $this->ctc()->is_legacy() ? $this : $file ) ); // support for plugins extension < 2.0
|
|
|
|
// read stylesheet
|
|
|
|
if ( $stylesheet_verified = $this->is_file_ok( $stylesheet, 'read' ) ):
|
|
/**
|
|
* Future use: make sure we have space to parse
|
|
*
|
|
if ( filesize( $stylesheet_verified ) * 3 > $this->ctc()->get_free_memory() ):
|
|
$this->max_sel = 1;
|
|
//$this->ctc()->debug( 'Insufficient memory to read file', __FUNCTION__, __CLASS__ );
|
|
return;
|
|
endif;
|
|
*/
|
|
$this->styles .= @file_get_contents( $stylesheet_verified ) . "\n";
|
|
//echo 'count after get contents: ' . strlen( $this->styles ) . LF;
|
|
else:
|
|
//echo 'not ok!' . LF;
|
|
endif;
|
|
}
|
|
|
|
/**
|
|
* parse_css
|
|
* Accepts raw CSS as text and parses into individual properties.
|
|
* FIXME - this function has grown too monolithic - refactor and componentize
|
|
* FIXME - migrate to event parser? handle comments?
|
|
*/
|
|
function parse_css( $template, $basequery = NULL, $parse_imports = TRUE, $relpath = '', $reset = FALSE ) {
|
|
//$this->load_config( 'sel_ndx' );
|
|
$this->load_config( 'val_ndx' );
|
|
$this->load_config( 'dict_query' );
|
|
$this->load_config( 'dict_sel' );
|
|
$this->load_config( 'dict_token' );
|
|
$this->load_config( 'dict_qs' );
|
|
$this->load_config( 'dict_val' );
|
|
$this->load_config( 'dict_rule' );
|
|
$this->load_config( 'dict_seq' );
|
|
if ( FALSE === strpos( $basequery, '@' ) ):
|
|
$basequery = 'base';
|
|
endif;
|
|
$ruleset = array();
|
|
// ignore commented code
|
|
$this->styles = preg_replace( '#\/\*.*?\*\/#s', '', $this->styles );
|
|
// space braces to ensure correct matching
|
|
$this->styles = preg_replace( '#([\{\}])\s*#', "$1\n", $this->styles );
|
|
// get all imports
|
|
if ( $parse_imports ):
|
|
|
|
$regex = '#(\@import\s+url\(.+?\));#';
|
|
preg_match_all( $regex, $this->styles, $matches );
|
|
foreach ( preg_grep( '#' . $this->get_prop( 'parnt' ) . '\/style\.css#', $matches[ 1 ], PREG_GREP_INVERT ) as $import ):
|
|
$import = preg_replace( "#^.*?url\(([^\)]+?)\).*#", "$1", $import );
|
|
$import = preg_replace( "#[\'\"]#", '', $import );
|
|
$import = '@import url(' . trim( $import ) . ')';
|
|
$this->imports[ $template ][ $import ] = 1;
|
|
endforeach;
|
|
if ( $this->ctc()->cache_updates ):
|
|
$this->ctc()->updates[] = array(
|
|
'obj' => 'imports',
|
|
'data' => array_keys( $this->imports[ $template ] ),
|
|
);
|
|
endif;
|
|
endif;
|
|
// break into @ segments
|
|
foreach ( array(
|
|
'#(\@media[^\{]+?)\{(\s*?)\}#', // get any placehoder (empty) media queries
|
|
'#(\@media[^\{]+?)\{(.*?\})?\s*?\}#s', // get all other media queries
|
|
) as $regex ): // (((?!\@media).) backreference too memory intensive - rolled back in v 1.4.8.1
|
|
preg_match_all( $regex, $this->styles, $matches );
|
|
foreach ( $matches[ 1 ] as $segment ):
|
|
$segment = $this->normalize_query( $segment );
|
|
$ruleset[ $segment ] = array_shift( $matches[ 2 ] )
|
|
. ( isset( $ruleset[ $segment ] ) ?
|
|
$ruleset[ $segment ] : '' );
|
|
endforeach;
|
|
// stripping rulesets leaves base styles
|
|
$this->styles = preg_replace( $regex, '', $this->styles );
|
|
endforeach;
|
|
$ruleset[ $basequery ] = $this->styles;
|
|
$qsid = NULL;
|
|
foreach ( $ruleset as $query => $segment ):
|
|
// make sure there is a newline before the first selector
|
|
$segment = LF . $segment;
|
|
// make sure there is semicolon before closing brace
|
|
$segment = preg_replace( '#(\})#', ";$1", $segment );
|
|
// parses selectors and corresponding rules
|
|
$regex = '#\n\s*([\[\.\#\:\w][\w\-\s\(\)\[\]\'\^\*\.\#\+\~:,"=>]+?)\s*\{(.*?)\}#s'; //[^\{] may be to expensive
|
|
preg_match_all( $regex, $segment, $matches );
|
|
foreach( $matches[ 1 ] as $sel ):
|
|
$stuff = array_shift( $matches[ 2 ] );
|
|
$this->update_arrays(
|
|
'child' == $template ? 'c' : 'p',
|
|
$query,
|
|
$sel
|
|
);
|
|
// handle base64 data
|
|
$stuff = preg_replace( '#data:([^;]+?);([^\)]+?)\)#s', "data:$1%%semi%%$2)", $stuff );
|
|
// rule semaphore makes sure rules are only reset the first time they appear
|
|
$resetrule = array();
|
|
foreach ( explode( ';', $stuff ) as $ruleval ):
|
|
if ( FALSE === strpos( $ruleval, ':' ) ) continue;
|
|
list( $rule, $value ) = explode( ':', $ruleval, 2 );
|
|
// trim, replace spaces with dashes, make lowercase
|
|
$rule = preg_replace( '/\s+/', '-', trim( strtolower( $rule ) ) );
|
|
$rule = preg_replace_callback( "/[^\w\-]/", array( $this, 'to_ascii' ), $rule );
|
|
// handle base64 data
|
|
$value = trim( str_replace( '%%semi%%', ';', $value ) );
|
|
|
|
$rules = $values = array();
|
|
// save important flag
|
|
$important = $this->is_important( $value );
|
|
// normalize color
|
|
$value = $this->normalize_color( $value );
|
|
// normalize font
|
|
if ( 'font' == $rule ):
|
|
$this->normalize_font( $value, $rules, $values );
|
|
// normalize background
|
|
elseif( 'background' == $rule ):
|
|
$this->normalize_background( $value, $rules, $values );
|
|
// normalize margin/padding
|
|
elseif ( 'margin' == $rule || 'padding' == $rule ):
|
|
$this->normalize_margin_padding( $rule, $value, $rules, $values );
|
|
else:
|
|
$rules[] = $rule;
|
|
$values[] = $value;
|
|
endif;
|
|
foreach ( $rules as $rule ):
|
|
$value = trim( array_shift( $values ) );
|
|
// normalize zero values
|
|
$value = preg_replace( '#\b0(px|r?em)#', '0', $value );
|
|
// normalize gradients
|
|
if ( FALSE !== strpos( $value, 'gradient' ) ):
|
|
if ( FALSE !== strpos( $rule, 'filter' ) ):
|
|
// treat as background-image, we'll add filter rule later
|
|
$rule = 'background-image';
|
|
continue;
|
|
endif;
|
|
if ( FALSE !== strpos( $value, 'webkit-gradient' ) ) continue; // bail on legacy webkit, we'll add it later
|
|
$value = $this->encode_gradient( $value );
|
|
endif;
|
|
// normalize common vendor prefixes
|
|
$rule = preg_replace( '#(\-(o|ms|moz|webkit)\-)?(' . implode( '|', $this->vendorrule ) . ')#', "$3", $rule );
|
|
if ( 'parnt' == $template && 'background-image' == $rule && strstr( $value, 'url(' ) )
|
|
$value = $this->convert_rel_url( $value, $relpath );
|
|
/**
|
|
* The reset flag forces the values for a given property (rule) to be rewritten completely
|
|
* when using the raw CSS input or when reading from a stylesheet.
|
|
* This permits complete blocks of style data to be entered verbatim, replacing existing styles.
|
|
* When entering individual values from the Query/Selector inputs, multiple fallback values for existing
|
|
* properties can be added in the order they are entered (e.g., margin: 1rem; margin: 1em;)
|
|
*/
|
|
if ( !$reset ) $resetrule[ $rule ] = TRUE;
|
|
|
|
$qsid = $this->update_arrays(
|
|
'child' == $template ? 'c' : 'p',
|
|
$query,
|
|
$sel,
|
|
$rule,
|
|
$value,
|
|
$important,
|
|
NULL, // no rulevalid is passed when parsing from css (vs post input data)
|
|
empty( $resetrule[ $rule ] ) // if rule semaphore is TRUE, reset will be FALSE
|
|
);
|
|
$resetrule[ $rule ] = TRUE; // set rule semaphore so if same rule occurs again, it is not reset
|
|
endforeach;
|
|
endforeach;
|
|
endforeach;
|
|
endforeach;
|
|
// if this is a raw css update pass the last selector back to the browser to update the form
|
|
if ( $this->ctc()->cache_updates && $qsid ):
|
|
$this->ctc()->updates[] = array(
|
|
'obj' => 'qsid',
|
|
'key' => $qsid,
|
|
'data' => $this->obj_to_utf8( $this->denorm_sel_val( $qsid ) ),
|
|
);
|
|
do_action( 'chld_thm_cfg_update_qsid', $qsid );
|
|
endif;
|
|
|
|
}
|
|
|
|
// converts relative path to absolute path for preview
|
|
function convert_rel_url( $value, $relpath, $url = TRUE ) {
|
|
if ( preg_match( '/data:/', $value ) ) return $value;
|
|
$path = preg_replace( '%url\([\'" ]*(.+?)[\'" ]*\)%', "$1", $value );
|
|
if ( preg_match( '%(https?:)?//%', $path ) ) return $value;
|
|
$pathparts = explode( '/', $path );
|
|
$fileparts = explode( '/', $relpath );
|
|
$newparts = array();
|
|
while ( $pathpart = array_shift( $pathparts ) ):
|
|
if ( '..' == $pathpart )
|
|
array_pop( $fileparts );
|
|
else array_push( $newparts, sanitize_text_field( $pathpart ) );
|
|
endwhile;
|
|
$newvalue = ( $url ? 'url(' : '' )
|
|
. ( $fileparts ? trailingslashit( implode( '/', $fileparts ) ) : '' )
|
|
. implode( '/', $newparts ) . ( $url ? ')' : '' );
|
|
$this->ctc()->debug( 'converted ' . $value . ' to ' . $newvalue . ' with ' . $relpath, __FUNCTION__, __CLASS__ );
|
|
return $newvalue;
|
|
}
|
|
|
|
/**
|
|
* write_css
|
|
* converts normalized CSS object data into stylesheet.
|
|
* Preserves selector sequence and !important flags of parent stylesheet.
|
|
* @media query blocks are sorted using internal heuristics (see sort_queries)
|
|
* New selectors are appended to the end of each media query block.
|
|
* FIXME - this function has grown too monolithic - refactor and componentize
|
|
*/
|
|
function write_css() {
|
|
$output = '';
|
|
foreach ( $this->sort_queries() as $query => $sort_order ):
|
|
$has_selector = 0;
|
|
$sel_output = '';
|
|
$selectors = $this->denorm_dict_qs( $query, FALSE );
|
|
uasort( $selectors, array( $this, 'cmp_seq' ) );
|
|
if ( 'base' != $query ) $sel_output .= $query . ' {' . LF;
|
|
foreach ( $selectors as $selid => $qsid ):
|
|
if ( $valarr = $this->unpack_val_ndx( $qsid ) ):
|
|
$sel = $this->detokenize( $this->get_dict_value( 'sel', $selid ) );
|
|
$shorthand = array();
|
|
$rule_output = array();
|
|
foreach ( $valarr as $ruleid => $temparr ):
|
|
// normalize values for backward compatability
|
|
if ( isset( $temparr[ 'c' ] ) &&
|
|
( !isset( $temparr[ 'p' ] ) || $temparr[ 'p' ] != $temparr[ 'c' ] ) ):
|
|
foreach ( $temparr[ 'c' ] as $rulevalarr ):
|
|
$this->add_vendor_rules(
|
|
$rule_output,
|
|
$shorthand,
|
|
$this->get_dict_value( 'rule', $ruleid ),
|
|
$this->get_dict_value( 'val', $rulevalarr[ 0 ] ),
|
|
$rulevalarr[ 1 ],
|
|
$rulevalarr[ 2 ]
|
|
);
|
|
endforeach;
|
|
/**
|
|
* for testing
|
|
else:
|
|
foreach ( $temparr[ 'parnt' ] as $rulevalarr ):
|
|
$this->add_vendor_rules(
|
|
$rule_output,
|
|
$shorthand,
|
|
$rulearr[ $ruleid ],
|
|
$valarr[ $rulevalarr[ 0 ] ],
|
|
$rulevalarr[ 1 ],
|
|
$rulevalarr[ 2 ]
|
|
);
|
|
endforeach;
|
|
*/
|
|
endif;
|
|
endforeach;
|
|
/** FIXME ** need better way to sort rules and multiple values ***/
|
|
$this->encode_shorthand( $shorthand, $rule_output );
|
|
if ( count( $rule_output ) ):
|
|
// show load order -- removed in v.1.7.6 by popular demand
|
|
//$sel_output .= isset( $this->dict_seq[ $qsid ] )?'/*' . $this->dict_seq[ $qsid ] . '*/' . LF:'';
|
|
$sel_output .= $sel . ' {' . LF . $this->stringify_rule_output( $rule_output ) . '}' . LF;
|
|
$has_selector = 1;
|
|
endif;
|
|
endif;
|
|
endforeach;
|
|
if ( 'base' != $query ) $sel_output .= '}' . LF;
|
|
if ( $has_selector ) $output .= $sel_output;
|
|
endforeach;
|
|
$output = $this->get_css_header_comment( $this->get_prop( 'handling' ) ) . LF . $output;
|
|
$stylesheet = $this->get_stylesheet_path();
|
|
$this->ctc()->debug( 'writing stylesheet: ' . $stylesheet, __FUNCTION__, __CLASS__ );
|
|
//echo //print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true) . LF;
|
|
if ( $stylesheet_verified = $this->is_file_ok( $stylesheet, 'write' ) ):
|
|
global $wp_filesystem; // this was initialized earlier;
|
|
$mode = 'direct' == $this->ctc()->fs_method ? FALSE : 0666;
|
|
// write new stylesheet:
|
|
// try direct write first, then wp_filesystem write
|
|
// stylesheet must already exist and be writable by web server
|
|
if ( $this->ctc()->is_ajax ):
|
|
if ( is_writable( $stylesheet_verified ) ):
|
|
$this->ctc()->debug( 'Attempting Ajax write...', __FUNCTION__, __CLASS__ );
|
|
if ( FALSE === @file_put_contents( $stylesheet_verified, $output ) ):
|
|
$this->ctc()->debug( 'Ajax write failed.', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
endif;
|
|
else:
|
|
$this->ctc()->debug( 'File not writable.', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
endif;
|
|
elseif ( FALSE === $wp_filesystem->put_contents( $this->ctc()->fspath( $stylesheet_verified ), $output, $mode ) ):
|
|
$this->ctc()->debug( 'Filesystem write failed.', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
endif;
|
|
$this->ctc()->debug( 'No write failure reported.', __FUNCTION__, __CLASS__ );
|
|
return TRUE;
|
|
endif;
|
|
$this->ctc()->debug( 'File NOT ok.', __FUNCTION__, __CLASS__ );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
function stringify_rule_output( &$rule_output ) {
|
|
$output = '';
|
|
asort( $rule_output );
|
|
foreach ( $rule_output as $rule => $sortstr )
|
|
$output .= ' ' . $rule . ";\n";
|
|
return $output;
|
|
}
|
|
|
|
function sortstr( $rule, $rulevalid ) {
|
|
return substr( "0000" . $this->get_dict_id( 'rule', $rule ), -4) . substr( "00" . $rulevalid, -2 );
|
|
}
|
|
|
|
/**
|
|
* encode_shorthand
|
|
* converts CTC long syntax into CSS shorthand
|
|
* v1.7.5 refactored for multiple values per property
|
|
* v2.1.0 to prevent incorrect rendering, do not use shorthand if multiple values exist for any side property.
|
|
*/
|
|
function encode_shorthand( $shorthand, &$rule_output ) {
|
|
foreach ( $shorthand as $property => $sides ):
|
|
if ( isset( $sides[ 'top' ] ) && 1 == count( $sides[ 'top' ] ) ):
|
|
foreach ( $sides[ 'top' ] as $tval => $tarr ):
|
|
if ( isset( $sides[ 'right' ] ) && 1 == count( $sides[ 'right' ] ) ):
|
|
$currseq = $tarr[ 1 ];
|
|
foreach ( $sides[ 'right' ] as $rval => $rarr ):
|
|
// value must exist from side and priority must match all sides
|
|
if ( isset( $sides[ 'bottom' ] ) && 1 == count( $sides[ 'bottom' ] ) && $tarr[ 0 ] == $rarr[ 0 ] ):
|
|
if ( $rarr[ 1 ] > $currseq ) $currseq = $rarr[ 1 ];
|
|
foreach ( $sides[ 'bottom' ] as $bval => $barr ):
|
|
if ( isset( $sides[ 'left' ] ) && 1 == count( $sides[ 'left' ] ) && $tarr[ 0 ] == $barr[ 0 ] ):
|
|
// use highest sort sequence of all sides
|
|
if ( $barr[ 1 ] > $currseq ) $currseq = $barr[ 1 ];
|
|
foreach ( $sides[ 'left' ] as $lval => $larr ):
|
|
if ( $tarr[ 0 ] != $larr[ 0 ] ) continue;
|
|
if ( $larr[ 1 ] > $currseq ) $currseq = $larr[ 1 ];
|
|
|
|
$combo = array(
|
|
$tval,
|
|
$rval,
|
|
$bval,
|
|
$lval,
|
|
);
|
|
// echo 'combo before: ' . print_r( $combo, TRUE ) . LF;
|
|
// remove from shorthand array
|
|
unset( $shorthand[ $property ][ 'top' ][ $tval ] );
|
|
unset( $shorthand[ $property ][ 'right' ][ $rval ] );
|
|
unset( $shorthand[ $property ][ 'bottom' ][ $bval ] );
|
|
unset( $shorthand[ $property ][ 'left' ][ $lval ] );
|
|
|
|
// combine into shorthand syntax
|
|
if ( $lval === $rval ):
|
|
//echo 'left same as right, popping left' . LF;
|
|
array_pop( $combo );
|
|
if ( $bval === $tval ):
|
|
//echo 'bottom same as top, popping bottom' . LF;
|
|
array_pop( $combo );
|
|
if ( $rval === $tval ): // && $bval === $tval ):
|
|
//echo 'right same as top, popping right' . LF;
|
|
array_pop( $combo );
|
|
endif;
|
|
endif;
|
|
endif;
|
|
//echo 'combo after: ' . print_r( $combo, TRUE ) . LF;
|
|
// set rule
|
|
$rule_output[ $property . ': ' . implode( ' ', $combo ) . ( $tarr[ 0 ] ? ' !important' : '' ) ] = $this->sortstr( $property, $currseq );
|
|
// reset sort sequence
|
|
$currseq = 0;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
// add remaining rules
|
|
foreach ( $shorthand as $property => $sides ):
|
|
foreach ( $sides as $side => $values ):
|
|
$rule = $property . '-' . $side;
|
|
foreach ( $values as $val => $valarr ):
|
|
// set rule
|
|
$rule_output[ $rule . ': ' . $val . ( $valarr[ 0 ] ? ' !important' : '' ) ] = $this->sortstr( $rule, $valarr[ 1 ] );
|
|
endforeach;
|
|
endforeach;
|
|
endforeach;
|
|
}
|
|
|
|
/**
|
|
* add_vendor_rules
|
|
* Applies vendor prefixes to rules/values and separates out shorthand properties .
|
|
* These are based on commonly used practices and not all vendor prefixes are supported.
|
|
* TODO: verify this logic against vendor and W3C documentation
|
|
*/
|
|
function add_vendor_rules( &$rule_output, &$shorthand, $rule, $value, $important, $rulevalid ) {
|
|
if ( '' === trim( $value ) ) return;
|
|
if ( 'filter' == $rule && ( FALSE !== strpos( $value, 'progid:DXImageTransform.Microsoft.Gradient' ) ) ) return;
|
|
$importantstr = $important ? ' !important' : '';
|
|
if ( preg_match( "/^(margin|padding)\-(top|right|bottom|left)$/", $rule, $matches ) ):
|
|
$shorthand[ $matches[ 1 ] ][ $matches[ 2 ] ][ $value ] = array(
|
|
$important,
|
|
$rulevalid,
|
|
);
|
|
return;
|
|
elseif ( preg_match( '/^(' . implode( '|', $this->vendorrule ) . ')$/', $rule ) ):
|
|
foreach( array( 'moz', 'webkit', 'o' ) as $prefix ):
|
|
$rule_output[ '-' . $prefix . '-' . $rule . ': ' . $value . $importantstr ] = $this->sortstr( $rule, $rulevalid++ );
|
|
endforeach;
|
|
$rule_output[ $rule . ': ' . $value . $importantstr ] = $this->sortstr( $rule, $rulevalid );
|
|
elseif ( 'background-image' == $rule ):
|
|
// gradient?
|
|
|
|
if ( $gradient = $this->decode_gradient( $value ) ):
|
|
// standard gradient
|
|
foreach( array( 'moz', 'webkit', 'o', 'ms' ) as $prefix ):
|
|
// build key before dereferencing array - v.2.3.0.3
|
|
$propkey = 'background-image: -' . $prefix . '-' . 'linear-gradient(' . $gradient[ 'origin' ] . ', '
|
|
. $gradient[ 'color1' ] . ', ' . $gradient[ 'color2' ] . ')' . $importantstr;
|
|
$rule_output[ $propkey ] = $this->sortstr( $rule, $rulevalid++ );
|
|
endforeach;
|
|
// W3C standard gradient
|
|
// rotate origin 90 degrees
|
|
if ( preg_match( '/(\d+)deg/', $gradient[ 'origin' ], $matches ) ):
|
|
$org = ( 90 - $matches[ 1 ] ) . 'deg';
|
|
else:
|
|
foreach ( preg_split( "/\s+/", $gradient[ 'origin' ] ) as $dir ):
|
|
$dir = strtolower( $dir );
|
|
$dirs[] = ( 'top' == $dir ? 'bottom' :
|
|
( 'bottom' == $dir ? 'top' :
|
|
( 'left' == $dir ? 'right' :
|
|
( 'right' == $dir ? 'left' : $dir ) ) ) );
|
|
endforeach;
|
|
$org = 'to ' . implode( ' ', $dirs );
|
|
endif;
|
|
// build key before dereferencing array - v.2.3.0.3
|
|
$propkey = 'background-image: linear-gradient(' . $org . ', '
|
|
. $gradient[ 'color1' ] . ', ' . $gradient[ 'color2' ] . ')' . $importantstr;
|
|
$rule_output[ $propkey ] = $this->sortstr( $rule, $rulevalid );
|
|
|
|
// legacy webkit gradient - we'll add if there is demand
|
|
// '-webkit-gradient(linear,' .$origin . ', ' . $color1 . ', '. $color2 . ')';
|
|
|
|
/**
|
|
* MS filter gradient - DEPRECATED in v1.7.5
|
|
* $type = ( in_array( $gradient[ 'origin' ], array( 'left', 'right', '0deg', '180deg' ) ) ? 1 : 0 );
|
|
* $color1 = preg_replace( "/^#/", '#00', $gradient[ 'color1' ] );
|
|
* $rule_output[ 'filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=' . $type . ', StartColorStr="'
|
|
* . strtoupper( $color1 ) . '", EndColorStr="' . strtoupper( $gradient[ 'color2' ] ) . '")'
|
|
* . $importantstr ] = $this->sortstr( $rule, $rulevalid );
|
|
*/
|
|
else:
|
|
// url or other value
|
|
$rule_output[ $rule . ': ' . $value . $importantstr ] = $this->sortstr( $rule, $rulevalid );
|
|
endif;
|
|
else:
|
|
$rule = preg_replace_callback( "/\d+/", array( $this, 'from_ascii' ), $rule );
|
|
$rule_output[ $rule . ': ' . $value . $importantstr ] = $this->sortstr( $rule, $rulevalid );
|
|
endif;
|
|
}
|
|
|
|
/**
|
|
* normalize_background
|
|
* parses background shorthand value and returns
|
|
* normalized rule/value pairs for each property
|
|
*/
|
|
function normalize_background( $value, &$rules, &$values ) {
|
|
if ( FALSE !== strpos( $value, 'gradient' ) ):
|
|
// only supporting linear syntax
|
|
if ( preg_match( '#(linear\-|Microsoft\.)#', $value ) ):
|
|
$values[] = $value;
|
|
$rules[] = 'background-image';
|
|
else:
|
|
// don't try to normalize non-linear gradients
|
|
$values[] = $value;
|
|
$rules[] = 'background';
|
|
endif;
|
|
else:
|
|
$regexes = array(
|
|
'image' => 'url *\\([^)]+?\\)|none',
|
|
'attachment' => 'scroll|fixed|local',
|
|
'clip' => '(padding|border|content)\\-box',
|
|
'repeat' => '(no\\-)?repeat(\\-(x|y))?|round|space',
|
|
'size' => 'cover|contain|auto',
|
|
'position' => 'top|bottom|left|right|center|\b0 +0\b|(\b0 +)?[\\-\\d.]+(px|%)( +0\b)?',
|
|
'color' => '\\#[a-fA-F0-9]{3,6}|(hsl|rgb)a? *\\([^)]+?\\)|[a-z]+'
|
|
);
|
|
//echo '<pre><code>' . "\n";
|
|
//echo '<strong>' . $value . '</strong>' . "\n";
|
|
foreach ( $regexes as $property => $regex ):
|
|
$this->temparray = array();
|
|
//echo $property . ': ' . $regex . "\n";
|
|
$value = preg_replace_callback( "/(" . $regex . ")/", array( $this, 'background_callback' ), $value );
|
|
if ( count( $this->temparray ) ):
|
|
$rules[] = 'background-' . $property;
|
|
$values[] = implode( ' ', $this->temparray );
|
|
//echo '<strong>result: ' . implode( ' ', $this->temparray ) . "</strong>\n";
|
|
endif;
|
|
endforeach;
|
|
//echo '</code></pre>' . "\n";
|
|
endif;
|
|
}
|
|
|
|
function background_callback( $matches ) {
|
|
$this->temparray[] = $matches[ 1 ];
|
|
}
|
|
|
|
/**
|
|
* normalize_font
|
|
* parses font shorthand value and returns
|
|
* normalized rule/value pairs for each property
|
|
*/
|
|
function normalize_font( $value, &$rules, &$values ) {
|
|
$regex = '#^((\d+|bold|normal) )?((italic|normal) )?(([\d\.]+(px|r?em|%))[\/ ])?(([\d\.]+(px|r?em|%)?) )?(.+)$#is';
|
|
preg_match( $regex, $value, $parts );
|
|
if ( !empty( $parts[ 2 ] ) ):
|
|
$rules[] = 'font-weight';
|
|
$values[] = $parts[ 2 ];
|
|
endif;
|
|
if ( !empty( $parts[ 4 ] ) ):
|
|
$rules[] = 'font-style';
|
|
$values[] = $parts[ 4 ];
|
|
endif;
|
|
if ( !empty( $parts[ 6 ] ) ):
|
|
$rules[] = 'font-size';
|
|
$values[] = $parts[ 6 ];
|
|
endif;
|
|
if ( !empty( $parts[ 9 ] ) ):
|
|
$rules[] = 'line-height';
|
|
$values[] = $parts[ 9 ];
|
|
endif;
|
|
if ( !empty( $parts[ 11 ] ) ):
|
|
$rules[] = 'font-family';
|
|
$values[] = $parts[ 11 ];
|
|
endif;
|
|
}
|
|
|
|
/**
|
|
* normalize_margin_padding
|
|
* parses margin or padding shorthand value and returns
|
|
* normalized rule/value pairs for each property
|
|
*/
|
|
function normalize_margin_padding( $rule, $value, &$rules, &$values ) {
|
|
$parts = preg_split( "/ +/", trim( $value ) );
|
|
if ( !isset( $parts[ 1 ] ) ) $parts[ 1 ] = $parts[ 0 ];
|
|
if ( !isset( $parts[ 2 ] ) ) $parts[ 2 ] = $parts[ 0 ];
|
|
if ( !isset( $parts[ 3 ] ) ) $parts[ 3 ] = $parts[ 1 ];
|
|
$rules[ 0 ] = $rule . '-top';
|
|
$values[ 0 ] = $parts[ 0 ];
|
|
$rules[ 1 ] = $rule . '-right';
|
|
$values[ 1 ] = $parts[ 1 ];
|
|
$rules[ 2 ] = $rule . '-bottom';
|
|
$values[ 2 ] = $parts[ 2 ];
|
|
$rules[ 3 ] = $rule . '-left';
|
|
$values[ 3 ] = $parts[ 3 ];
|
|
}
|
|
|
|
/**
|
|
* encode_gradient
|
|
* Normalize linear gradients from a bazillion formats into standard CTC syntax.
|
|
* This has been refactored in v1.7.5 to accommodate new spectrum color picker color "names."
|
|
* Currently only supports two-color linear gradients with no inner stops.
|
|
* TODO: legacy webkit? more gradients?
|
|
*/
|
|
function encode_gradient( $value ) {
|
|
// don't try this at home, kids
|
|
$regex = '/gradient[^\)]*?\( #exp descr
|
|
( #[1]
|
|
( #[2]
|
|
(to\x20)? #[3] reverse
|
|
(top|bottom|left|right)? #[4] direction1
|
|
(\x20 #[5]
|
|
(top|bottom|left|right))? #[6] direction2
|
|
|\d+deg),)? # or angle
|
|
(color-stop\()? #[7] optional
|
|
([^\w\#\)]*[\'"]? #[8]
|
|
(\#\w{3,8} #[9] color (hex)
|
|
|rgba?\([\d.,\x20]+?\) # red green blue (alpha)
|
|
|hsla?\([\d%.,\x20]+?\) # hue sat. lum. (alpha)
|
|
|[a-z]+) # color (name)
|
|
(\x20+[\d.]+%?)?) #[10] stop position
|
|
(\),\x20*)? #[11] optional close
|
|
(color-stop\()? #[12] optional
|
|
([^\w\#\)]*[\'"]? #[13]
|
|
(\#\w{3,8} #[14] color (hex)
|
|
|rgba?\([\d.,\x20]+?\) # red green blue (alpha)
|
|
|hsla?\([\d%.,\x20]+?\) # hue sat. lum. (alpha)
|
|
|[a-z]+) # color (name)
|
|
(\x20+[\d.]+%?)?) #[15] stop position
|
|
(\))? #[16] optional close
|
|
([^\w\)]*gradienttype=[\'"]? #[17] IE
|
|
(\d) #[18] IE
|
|
[\'"]?)? # IE
|
|
[^\w\)]*\)/ix';
|
|
$param = $parts = array();
|
|
preg_match( $regex, $value, $parts );
|
|
//$this->ctc()->debug( 'gradient value: ' . $value . ' parts: ' . print_r( $parts, TRUE ), __FUNCTION__, __CLASS__ );
|
|
if ( empty( $parts[ 18 ] ) ):
|
|
if ( empty( $parts[ 2 ] ) ):
|
|
$param[ 0 ] = 'top';
|
|
elseif ( 'to ' == $parts[ 3 ] ):
|
|
|
|
$param[ 0 ] = ( 'top' == $parts[ 4 ] ? 'bottom' :
|
|
( 'left' == $parts[ 4 ] ? 'right' :
|
|
( 'right' == $parts[ 4 ] ? 'left' :
|
|
'top' ) ) ) ;
|
|
else:
|
|
$param[ 0 ] = trim( $parts[ 2 ] );
|
|
endif;
|
|
if ( empty( $parts[ 10 ] ) ):
|
|
$param[ 2 ] = '0%';
|
|
else:
|
|
$param[ 2 ] = trim( $parts[ 10 ] );
|
|
endif;
|
|
if ( empty( $parts[ 15 ] ) ):
|
|
$param[ 4 ] = '100%';
|
|
else:
|
|
$param[ 4 ] = trim( $parts[ 15 ] );
|
|
endif;
|
|
elseif( '0' == $parts[ 18 ] ):
|
|
$param[ 0 ] = 'top';
|
|
$param[ 2 ] = '0%';
|
|
$param[ 4 ] = '100%';
|
|
elseif ( '1' == $parts[ 18 ] ):
|
|
$param[ 0 ] = 'left';
|
|
$param[ 2 ] = '0%';
|
|
$param[ 4 ] = '100%';
|
|
endif;
|
|
if ( isset( $parts[ 9 ] ) && isset( $parts[ 14 ] ) ):
|
|
$param[ 1 ] = $parts[ 9 ];
|
|
$param[ 3 ] = $parts[ 14 ];
|
|
ksort( $param );
|
|
return implode( ':', $param );
|
|
else:
|
|
return $value;
|
|
endif;
|
|
}
|
|
|
|
/**
|
|
* decode_border
|
|
* De-normalize CTC border syntax into individual properties.
|
|
*/
|
|
function decode_border( $value ) {
|
|
$parts = preg_split( '#\s+#', $value, 3 );
|
|
if ( 1 == count( $parts ) ):
|
|
$parts[ 0 ] = $value;
|
|
$parts[ 1 ] = $parts[ 2 ] = '';
|
|
endif;
|
|
return array(
|
|
'width' => empty( $parts[ 0 ] ) ? '' : $parts[ 0 ],
|
|
'style' => empty( $parts[ 1 ] ) ? '' : $parts[ 1 ],
|
|
'color' => empty( $parts[ 2 ] ) ? '' : $parts[ 2 ],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* decode_gradient
|
|
* Decode CTC gradient syntax into individual properties.
|
|
*/
|
|
function decode_gradient( $value ) {
|
|
$parts = explode( ':', $value, 5 );
|
|
if ( !preg_match( '#(url|none)#i', $value ) && 5 == count( $parts ) ):
|
|
return array(
|
|
'origin' => empty( $parts[ 0 ] ) ? '' : $parts[ 0 ],
|
|
'color1' => empty( $parts[ 1 ] ) ? '' : $parts[ 1 ],
|
|
'stop1' => empty( $parts[ 2 ] ) ? '' : $parts[ 2 ],
|
|
'color2' => empty( $parts[ 3 ] ) ? '' : $parts[ 3 ],
|
|
'stop2' => empty( $parts[ 4 ] ) ? '' : $parts[ 4 ],
|
|
);
|
|
endif;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* denorm_rule_val
|
|
* Return array of unique values corresponding to specific rule
|
|
* FIXME: only return child if no original value exists
|
|
*/
|
|
function denorm_rule_val( $ruleid ) {
|
|
$this->load_config( 'dict_val' );
|
|
$this->load_config( 'val_ndx' );
|
|
$rule_sel_arr = array();
|
|
foreach ( $this->val_ndx as $qsid => $p ):
|
|
if ( $valarr = $this->unpack_val_ndx( $qsid ) ):
|
|
if ( !isset( $valarr[ $ruleid ] ) ) continue;
|
|
foreach ( array( 'p', 'c' ) as $template ):
|
|
if ( isset( $valarr[ $ruleid ][ $template ] ) ):
|
|
foreach ( $valarr[ $ruleid ][ $template ] as $rulevalarr ):
|
|
$rule_sel_arr[ $rulevalarr[ 0 ] ] = $this->get_dict_value( 'val', $rulevalarr[ 0 ] );
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
return $rule_sel_arr;
|
|
}
|
|
|
|
/**
|
|
* denorm_val_query
|
|
* Return array of queries, selectors, rules, and values corresponding to
|
|
* specific rule/value combo grouped by query, selector
|
|
* FIXME: only return new values corresponding to specific rulevalid of matching original value
|
|
*/
|
|
function denorm_val_query( $valid, $rule ) {
|
|
$this->load_config( 'dict_rule' );
|
|
$this->load_config( 'val_ndx' );
|
|
$value_query_arr = array();
|
|
if ( $thisruleid = $this->get_dict_id( 'rule', $rule ) ):
|
|
foreach ( $this->val_ndx as $qsid => $p ):
|
|
if ( $valarr = $this->unpack_val_ndx( $qsid ) ):
|
|
foreach ( $valarr as $ruleid => $values ):
|
|
if ( $ruleid != $thisruleid ) continue;
|
|
foreach ( array( 'p', 'c' ) as $template ):
|
|
if ( isset( $values[ $template ] ) ):
|
|
foreach ( $values[ $template ] as $rulevalarr ):
|
|
if ( $rulevalarr[ 0 ] != $valid ) continue;
|
|
$selarr = $this->denorm_query_sel( $qsid );
|
|
$value_query_arr[ $rule ][ $selarr[ 'query' ] ][ $qsid ] = $this->denorm_sel_val( $qsid );
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endif;
|
|
return $value_query_arr;
|
|
}
|
|
|
|
/**
|
|
* denorm_query_sel
|
|
* Return id, query and selector values of a specific qsid (query-selector ID)
|
|
*/
|
|
function denorm_query_sel( $qsid ) {
|
|
$this->load_config( 'dict_query' );
|
|
$this->load_config( 'dict_sel' );
|
|
$this->load_config( 'dict_seq' );
|
|
$this->load_config( 'dict_qs' );
|
|
$this->load_config( 'dict_token' );
|
|
if ( FALSE === ( $qs = $this->get_dict_value( 'qs', $qsid ) ) ):
|
|
$this->ctc()->debug( $qsid . ' does not exist', __FUNCTION__, __CLASS__ );
|
|
return array();
|
|
endif;
|
|
list( $q, $s ) = explode( ':', $qs );
|
|
if ( $seq = $this->get_dict_value( 'seq', $qsid ) ):
|
|
$this->ctc()->debug( 'get seq: custom: ' . $seq, __FUNCTION__, __CLASS__ );
|
|
else:
|
|
$seq = $qsid;
|
|
$this->ctc()->debug( 'get seq: using qsid: ' . $qsid, __FUNCTION__, __CLASS__ );
|
|
endif;
|
|
$qselarr = array(
|
|
'id' => $qsid,
|
|
'query' => $this->get_dict_value( 'query', $q ),
|
|
'selector' => $this->detokenize( $this->get_dict_value( 'sel', $s ) ),
|
|
'seq' => $seq,
|
|
);
|
|
return $qselarr;
|
|
}
|
|
|
|
/**
|
|
* denorm_sel_val
|
|
* Return array of rules, and values matching specific qsid (query-selector ID)
|
|
* grouped by query, selector
|
|
*/
|
|
function denorm_sel_val( $qsid ) {
|
|
$this->load_config( 'dict_val' );
|
|
$this->load_config( 'dict_rule' );
|
|
$this->load_config( 'val_ndx' );
|
|
$selarr = $this->denorm_query_sel( $qsid );
|
|
if ( $valarr = $this->unpack_val_ndx( $qsid ) ):
|
|
//$this->ctc()->debug( 'valarr: ' . print_r( $valarr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
foreach ( $valarr as $ruleid => $values ):
|
|
//$this->ctc()->debug( 'ruleid: ' . $ruleid, __FUNCTION__, __CLASS__ );
|
|
foreach ( array( 'p', 'c' ) as $template ):
|
|
$t = 'c' == $template ? 'child' : 'parnt';
|
|
//$this->ctc()->debug( 'template: ' . $t, __FUNCTION__, __CLASS__ );
|
|
if ( isset( $values[ $template ] ) ):
|
|
foreach ( $values[ $template ] as $rulevalarr ):
|
|
$selarr[ 'value' ][ $this->get_dict_value( 'rule', $ruleid ) ][ $t ][] = array(
|
|
$this->get_dict_value( 'val', $rulevalarr[ 0 ] ),
|
|
$rulevalarr[ 1 ],
|
|
isset( $rulevalarr[ 2 ] ) ? $rulevalarr[ 2 ] : 1,
|
|
);
|
|
endforeach;
|
|
endif;
|
|
endforeach;
|
|
endforeach;
|
|
endif;
|
|
//$this->ctc()->debug( print_r( $selarr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
return $selarr;
|
|
}
|
|
|
|
/**
|
|
/**
|
|
* v1.7.5
|
|
* convert and/or normalize rule/value index
|
|
* to support multiple values per property ( rule )
|
|
* allows backward compatility with < v1.7.5
|
|
*/
|
|
function convert_ruleval_array( &$arr ) {
|
|
//$this->ctc()->debug( 'original array: ' . print_r( $arr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
foreach ( array( 'parnt', 'child' ) as $template ):
|
|
// skip if empty array
|
|
if ( !isset( $arr[ $template ] ) ) continue;
|
|
$t = 'child' == $template ? 'c' : 'p';
|
|
// check if using original data structure ( value is scalar )
|
|
if ( ! is_array( $arr[ $template ] ) ):
|
|
/**
|
|
* create new array to replace old scalar value
|
|
* value structure is
|
|
* [0] => value
|
|
* [1] => important
|
|
* [2] => priority
|
|
*/
|
|
$arr[ $t ] = array( array( $arr[ $template ], $arr[ 'i_' . $template ], 0, 1 ) );
|
|
else:
|
|
$arr[ $t ] = $arr[ $template ];
|
|
endif;
|
|
unset( $arr[ $template ] );
|
|
endforeach;
|
|
//$this->ctc()->debug( 'first pass: ' . print_r( $arr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
foreach ( array( 'p', 'c' ) as $template ):
|
|
if ( !isset( $arr[ $template ] ) ) continue;
|
|
$newarr = array();
|
|
// iterate each value and enforce array structure
|
|
foreach ( $arr[ $template ] as $rulevalid => $rulevalarr ):
|
|
// skip if empty array
|
|
if ( empty ( $rulevalarr ) ) continue;
|
|
//
|
|
if ( ! is_array( $rulevalarr ) ):
|
|
// important flag moves to individual value in array
|
|
$important = isset( $arr[ 'i_' . $template ] ) ? $arr[ 'i_' . $template ] : 0;
|
|
unset( $arr[ 'i_' . $template ] );
|
|
$val = (int) $rulevalarr;
|
|
$rulevalarr = array( $val, $important, $rulevalid );
|
|
elseif ( !isset( $rulevalarr[ 2 ] ) ):
|
|
$rulevalarr[ 2 ] = $rulevalid;
|
|
endif;
|
|
$newarr[] = $rulevalarr;
|
|
endforeach;
|
|
$arr[ $template ] = $newarr;
|
|
endforeach;
|
|
//$this->ctc()->debug( 'second pass: ' . print_r( $arr, TRUE ), __FUNCTION__, __CLASS__ );
|
|
}
|
|
|
|
/**
|
|
* Convert all internal data dictionaries to latest format.
|
|
*/
|
|
function convert_dict_arrays(){
|
|
$this->ctc()->debug( 'converting dictionaries from old format', __FUNCTION__, __CLASS__ );
|
|
foreach ( $this->dicts as $dict => $loaded ):
|
|
$this->load_config( $dict );
|
|
switch ( $dict ):
|
|
case 'dict_seq':
|
|
case 'dict_token':
|
|
break;
|
|
case 'sel_ndx':
|
|
$this->{ $dict } = array();
|
|
break;
|
|
case 'val_ndx':
|
|
foreach ( $this->val_ndx as $qsid => $rulearr ):
|
|
foreach ( $rulearr as $ruleid => $valarr )
|
|
$this->convert_ruleval_array( $this->val_ndx[ $qsid ][ $ruleid ] );
|
|
$this->pack_val_ndx( $qsid, $this->val_ndx[ $qsid ] );
|
|
endforeach;
|
|
break;
|
|
case 'dict_qs':
|
|
$qsarr = array();
|
|
foreach ( $this->dict_qs as $qsid => $arr ):
|
|
$qs = $arr[ 'q' ] . ':' . $arr[ 's' ];
|
|
$qsarr[ $qsid ] = $qs;
|
|
endforeach;
|
|
$this->dict_qs = $qsarr;
|
|
break;
|
|
default:
|
|
$this->{ $dict } = array_flip( $this->{ $dict } );
|
|
foreach ( $this->{ $dict } as $key => $val ):
|
|
if ( 'dict_sel' == $dict )
|
|
$this->dict_sel[ $key ] = $this->tokenize( (string) $val );
|
|
else
|
|
$this->{ $dict }[ $key ] = ( string ) $val;
|
|
endforeach;
|
|
endswitch;
|
|
//echo '<pre><code><small><strong>' . $dict . '</strong>' . print_r( $this->{ $dict }, TRUE) . '</small></code></pre>' . LF;
|
|
endforeach;
|
|
$this->save_config();
|
|
}
|
|
|
|
/**
|
|
* denorm_dict_qs
|
|
* Return denormalized array containing query and selector heirarchy
|
|
*/
|
|
function denorm_dict_qs( $query = NULL, $norm = TRUE ) {
|
|
$this->load_config( 'dict_query' );
|
|
$this->load_config( 'dict_sel' );
|
|
$this->load_config( 'dict_token' );
|
|
$this->load_config( 'dict_qs' );
|
|
$retarray = array();
|
|
if ( $query ):
|
|
$q = $this->get_dict_id( 'query', $query );
|
|
$selarr = preg_grep( '/^' . $q . ':/', $this->dict_qs );
|
|
foreach ( $selarr as $qsid => $qs ):
|
|
list( $q, $s ) = explode( ':', $qs );
|
|
if ( $norm )
|
|
$retarray[ $qsid ] = $this->detokenize( $this->get_dict_value( 'sel', $s ) );
|
|
else
|
|
$retarray[ $s ] = $qsid;
|
|
endforeach;
|
|
else:
|
|
return array_values( $this->dict_query );
|
|
endif;
|
|
if ( $norm )
|
|
return $this->sort_selectors( $retarray );
|
|
return $retarray;
|
|
}
|
|
|
|
/**
|
|
* is_important
|
|
* Strip important flag from value reference and return boolean
|
|
* Updating two values at once
|
|
*/
|
|
function is_important( &$value ) {
|
|
$important = 0;
|
|
$value = trim( str_ireplace( '!important', '', $value, $important ) );
|
|
return $important;
|
|
}
|
|
|
|
/**
|
|
* sort_queries
|
|
* De-normalize query data and return array sorted as follows:
|
|
* base
|
|
* @media max-width queries in descending order
|
|
* other @media queries in no particular order
|
|
* @media min-width queries in ascending order
|
|
*/
|
|
function sort_queries() {
|
|
$this->load_config( 'dict_query' );
|
|
$queries = array();
|
|
foreach ( $this->dict_query as $queryid => $query ):
|
|
/** lookup **/
|
|
if ( 'base' == $query ):
|
|
$queries[ 'base' ] = -999999;
|
|
continue;
|
|
endif;
|
|
if ( preg_match( "/((min|max)(\-device)?\-width)\s*:\s*(\d+)/", $query, $matches ) ):
|
|
$queries[ $query ] = 'min-width' == $matches[ 1 ] ? $matches[ 4 ] : -$matches[ 4 ];
|
|
else:
|
|
$queries[ $query ] = $queryid - 10000;
|
|
endif;
|
|
endforeach;
|
|
asort( $queries );
|
|
return $queries;
|
|
}
|
|
|
|
function sort_selectors( $selarr ) {
|
|
$selarr = ( array ) $selarr;
|
|
uasort( $selarr, array( $this, 'cmp_sel' ) );
|
|
return array_flip( $selarr );
|
|
}
|
|
|
|
function cmp_sel( $a, $b ) {
|
|
$cmpa = preg_replace( "/\W/", '', $a );
|
|
$cmpb = preg_replace( "/\W/", '', $b );
|
|
if ( $cmpa == $cmpb ) return 0;
|
|
return ( $cmpa < $cmpb ) ? -1 : 1;
|
|
}
|
|
|
|
// sort selectors based on dict_seq if exists, otherwise qsid
|
|
function cmp_seq( $a, $b ) {
|
|
if ( FALSE === ( $cmpa = $this->get_dict_value( 'seq', $a ) ) )
|
|
$cmpa = $a;
|
|
if ( FALSE === ( $cmpb = $this->get_dict_value( 'seq', $b ) ) )
|
|
$cmpb = $b;
|
|
if ( $cmpa == $cmpb ) return 0;
|
|
return ( $cmpa < $cmpb ) ? -1 : 1;
|
|
}
|
|
|
|
/**
|
|
* obj_to_utf8
|
|
* sets object data to UTF8
|
|
* flattens to array
|
|
* and stringifies NULLs
|
|
*/
|
|
function obj_to_utf8( $data ) {
|
|
if ( is_object( $data ) )
|
|
$data = get_object_vars( $data );
|
|
if ( is_array( $data ) )
|
|
return array_map( array( &$this, __FUNCTION__ ), $data );
|
|
else
|
|
return is_null( $data ) ? '' : utf8_encode( $data );
|
|
}
|
|
|
|
// convert ascii character into decimal value
|
|
function to_ascii( $matches ) {
|
|
return ord( $matches[ 0 ] );
|
|
}
|
|
|
|
// convert decimal value into ascii character
|
|
function from_ascii( $matches ) {
|
|
return chr( $matches[ 0 ] );
|
|
}
|
|
|
|
/**
|
|
* is_file_ok
|
|
* verify file exists and is in valid location
|
|
* must be in theme or plugin folders
|
|
*/
|
|
function is_file_ok( $stylesheet, $permission = 'read' ) {
|
|
// remove any ../ manipulations
|
|
$stylesheet = $this->ctc()->normalize_path( preg_replace( "%\.\./%", '/', $stylesheet ) );
|
|
//$this->ctc()->debug( 'checking file: ' . $stylesheet, __FUNCTION__, __CLASS__ );
|
|
if ( 'read' == $permission && !is_file( $stylesheet ) ):
|
|
$this->ctc()->debug( 'read ' . $stylesheet . ' no file!', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
elseif ( 'write' == $permission && !is_dir( dirname( $stylesheet ) ) ):
|
|
$this->ctc()->debug( 'write ' . $stylesheet . ' no dir!', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
elseif ( 'search' == $permission && !is_dir( $stylesheet ) ):
|
|
$this->ctc()->debug( 'search ' . $stylesheet . ' no dir!', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
endif;
|
|
// check if in themes dir;
|
|
$regex = '%^' . preg_quote( $this->ctc()->normalize_path( get_theme_root() ) ) . '%';
|
|
//$this->ctc()->debug( 'theme regex: ' . $regex, __FUNCTION__, __CLASS__ );
|
|
if ( preg_match( $regex, $stylesheet ) ):
|
|
//$this->ctc()->debug( $stylesheet . ' ok!', __FUNCTION__, __CLASS__ );
|
|
return $stylesheet;
|
|
endif;
|
|
// check if in plugins dir
|
|
$regex = '%^' . preg_quote( $this->ctc()->normalize_path( WP_PLUGIN_DIR ) ) . '%';
|
|
//$this->ctc()->debug( 'plugin regex: ' . $regex, __FUNCTION__, __CLASS__ );
|
|
if ( preg_match( $regex, $stylesheet ) ):
|
|
//$this->ctc()->debug( $stylesheet . ' ok!', __FUNCTION__, __CLASS__ );
|
|
return $stylesheet;
|
|
endif;
|
|
$this->ctc()->debug( $stylesheet . ' is not in wp folders!', __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* normalize_color
|
|
* Sets hex string to lowercase and shortens to 3 char format if possible
|
|
*/
|
|
function normalize_color( $value ) {
|
|
$value = preg_replace_callback( "/#([0-9A-F]{3}([0-9A-F]{3})?)/i", array( $this, 'tolower' ), $value );
|
|
$value = preg_replace( "/#([0-9A-F])\\1([0-9A-F])\\2([0-9A-F])\\3/i", "#$1$2$3", $value );
|
|
return $value;
|
|
}
|
|
|
|
function normalize_query( $value ) {
|
|
// space after :
|
|
$value = str_replace( ':', ': ', trim( $value ) );
|
|
// remove multiple whitespace
|
|
$value = preg_replace( "/\s+/s", ' ', $value );
|
|
// remove space after (
|
|
$value = str_replace( '( ', '(', $value );
|
|
// remove space before )
|
|
$value = str_replace( ' )', ')', $value );
|
|
return $value;
|
|
}
|
|
|
|
// callback for normalize_color regex
|
|
function tolower( $matches ) {
|
|
return '#' . strtolower( $matches[ 1 ] );
|
|
}
|
|
|
|
function tokenize( $value ){
|
|
return $value;
|
|
// swap out commas and/or consecutive alphas with leading non-alpha if present
|
|
$value = preg_replace_callback( "/(, |[_\W]?[^\W_]+)/", array( $this, 'get_token' ), $value );
|
|
// trim off leading/trailing delimiter
|
|
$value = preg_replace( "/^%%|%%$/", '', $value );
|
|
// split into packable array
|
|
$array = array_map( array( $this, 'to_int' ), preg_split( "/(%%)+/", $value ) );
|
|
//echo '<pre><code><small>';
|
|
//var_dump( $array );
|
|
//echo '</small></code></pre>';
|
|
try {
|
|
return $this->packer->encode( $this->packer->pack( $array ) );
|
|
} catch ( Exception $e ) {
|
|
$this->ctc()->debug( 'Pack failed -- ' . $e->getMessage(), __FUNCTION__, __CLASS__ );
|
|
}
|
|
}
|
|
|
|
function to_int( $val ){
|
|
return intval( $val ) == $val ? (int) $val : $val;
|
|
}
|
|
|
|
function detokenize( $packed ){
|
|
return $packed;
|
|
// unpack array
|
|
try {
|
|
$this->packer->reset( $this->packer->decode( $packed ) );
|
|
$array = $this->packer->unpack();
|
|
} catch ( Exception $e ) {
|
|
$this->ctc()->debug( 'Unpack failed -- ' . $e->getMessage(), __FUNCTION__, __CLASS__ );
|
|
return FALSE;
|
|
}
|
|
$unpacked = array();
|
|
// iterate array and replace tokens
|
|
do {
|
|
$token = array_shift( $array );
|
|
if ( 'integer' == gettype( $token ) )
|
|
$unpacked[] = $this->get_dict_value( 'token', $token );
|
|
else
|
|
$unpacked[] = $token;
|
|
} while( $array );
|
|
|
|
// assemble array
|
|
return implode( '', $unpacked );
|
|
}
|
|
|
|
function get_token( $matches ){
|
|
$token = $matches[ 1 ];
|
|
$id = $this->get_dict_id( 'token', $token );
|
|
$this->instances[ $id ] = isset( $this->instances[ $id ] )
|
|
? $this->instances[ $id ] + 1
|
|
: 1;
|
|
return '%%' . $id . '%%';
|
|
}
|
|
} |