PageRenderTime 33ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Function.php

http://github.com/peteboere/css-crush
PHP | 353 lines | 234 code | 66 blank | 53 comment | 34 complexity | d365bf8272c6863c6d923d4fb57b6c84 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * Custom CSS functions
  5. *
  6. */
  7. class CssCrush_Function {
  8. // Regex pattern for finding custom functions
  9. public static $functionPatt;
  10. // Cache for function names
  11. public static $functionList;
  12. public static function init () {
  13. // Set the custom function regex pattern
  14. self::$functionList = self::getFunctions();
  15. self::$functionPatt = '!(^|[^a-z0-9_-])(' . implode( '|', self::$functionList ) . ')?\(!i';
  16. }
  17. public static function getFunctions () {
  18. // Fetch custom function names
  19. // Include subtraction operator
  20. $fn_methods = array( '-' );
  21. $all_methods = get_class_methods( __CLASS__ );
  22. foreach ( $all_methods as &$_method ) {
  23. $prefix = 'css_fn__';
  24. if ( ( $pos = strpos( $_method, $prefix ) ) === 0 ) {
  25. $fn_methods[] = str_replace( '_', '-', substr( $_method, strlen( $prefix ) ) );
  26. }
  27. }
  28. return $fn_methods;
  29. }
  30. public static function parseAndExecuteValue ( $str ) {
  31. $patt = self::$functionPatt;
  32. // No bracketed expressions, early return
  33. if ( false === strpos( $str, '(' ) ) {
  34. return $str;
  35. }
  36. // No custom functions, early return
  37. if ( !preg_match( $patt, $str ) ) {
  38. return $str;
  39. }
  40. // Need a space inside the front function paren for the following match_all to be reliable
  41. $str = preg_replace( '!\(([^\s])!', '( $1', $str, -1, $spacing_count );
  42. // Find custom function matches
  43. $match_count = preg_match_all( $patt, $str, $m, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
  44. // Step through the matches from last to first
  45. while ( $match = array_pop( $m ) ) {
  46. $offset = $match[0][1];
  47. $before_char = $match[1][0];
  48. $before_char_len = strlen( $before_char );
  49. // No function name default to math expression
  50. // Store the raw function name match
  51. $raw_fn_name = isset( $match[2] ) ? $match[2][0] : '';
  52. $fn_name = $raw_fn_name ? $raw_fn_name : 'math';
  53. if ( '-' === $fn_name ) {
  54. $fn_name = 'math';
  55. }
  56. // Loop throught the string
  57. $first_paren_offset = strpos( $str, '(', $offset );
  58. $paren_score = 0;
  59. for ( $index = $first_paren_offset; $index < strlen( $str ); $index++ ) {
  60. $char = $str[ $index ];
  61. if ( '(' === $char ) {
  62. $paren_score++;
  63. }
  64. elseif ( ')' === $char ) {
  65. $paren_score--;
  66. }
  67. if ( 0 === $paren_score ) {
  68. // Get the function inards
  69. $content_start = $offset + strlen( $before_char ) + strlen( $raw_fn_name ) + 1;
  70. $content_finish = $index;
  71. $content = substr( $str, $content_start, $content_finish - $content_start );
  72. $content = trim( $content );
  73. // Calculate offsets
  74. $func_start = $offset + strlen( $before_char );
  75. $func_end = $index + 1;
  76. // Workaround the minus
  77. $minus_before = '-' === $raw_fn_name ? '-' : '';
  78. $result = '';
  79. if ( in_array( $fn_name, self::$functionList ) ) {
  80. $fn_name_clean = str_replace( '-', '_', $fn_name );
  81. $result = call_user_func( array( 'self', "css_fn__$fn_name_clean" ), $content );
  82. }
  83. // Join together the result
  84. $str = substr( $str, 0, $func_start ) . $minus_before . $result . substr( $str, $func_end );
  85. break;
  86. }
  87. }
  88. } // while
  89. // Restore the whitespace
  90. if ( $spacing_count ) {
  91. $str = str_replace( '( ', '(', $str );
  92. }
  93. return $str;
  94. }
  95. ############
  96. # Helpers
  97. protected static function parseMathArgs ( $input ) {
  98. // Split on comma, trim, and remove empties
  99. $args = array_filter( array_map( 'trim', explode( ',', $input ) ) );
  100. // Pass anything non-numeric through math
  101. foreach ( $args as &$arg ) {
  102. if ( !preg_match( '!^-?[\.0-9]+$!', $arg ) ) {
  103. $arg = self::css_fn__math( $arg );
  104. }
  105. }
  106. return $args;
  107. }
  108. protected static function parseArgs ( $input, $allowSpaceDelim = false ) {
  109. $args = CssCrush::splitDelimList( $input,
  110. ( $allowSpaceDelim ? '\s*[,\s]\s*' : ',' ),
  111. true, true );
  112. return array_map( 'trim', $args->list );
  113. }
  114. protected static function colorAdjust ( $color, array $adjustments ) {
  115. $fn_matched = preg_match( '!^(#|rgba?|hsla?)!', $color, $m );
  116. $keywords = CssCrush_Color::getKeywords();
  117. // Support for Hex, RGB, RGBa and keywords
  118. // HSL and HSLa are passed over
  119. if ( $fn_matched or array_key_exists( $color, $keywords ) ) {
  120. $alpha = 1;
  121. $rgb = null;
  122. // Get an RGB array from the color argument
  123. if ( $fn_matched ) {
  124. switch ( $m[1] ) {
  125. case '#':
  126. $rgb = CssCrush_Color::hexToRgb( $color );
  127. break;
  128. case 'rgb':
  129. case 'rgba':
  130. case 'hsl':
  131. case 'hsla':
  132. $function = $m[1];
  133. $alpha_channel = 4 === strlen( $function ) ? true : false;
  134. $vals = substr( $color, strlen( $function ) + 1 ); // Trim function name and start paren
  135. $vals = substr( $vals, 0, strlen( $vals ) - 1 ); // Trim end paren
  136. $vals = array_map( 'trim', explode( ',', $vals ) ); // Explode to array of arguments
  137. if ( $alpha_channel ) {
  138. $alpha = array_pop( $vals );
  139. }
  140. if ( 0 === strpos( $function, 'rgb' ) ) {
  141. $rgb = CssCrush_Color::normalizeCssRgb( $vals );
  142. }
  143. else {
  144. $rgb = CssCrush_Color::cssHslToRgb( $vals );
  145. }
  146. break;
  147. }
  148. }
  149. else {
  150. $rgb = $keywords[ $color ];
  151. }
  152. $hsl = CssCrush_Color::rgbToHsl( $rgb );
  153. // Normalize adjustment parameters to floating point numbers
  154. // then calculate the new HSL value
  155. $index = 0;
  156. foreach ( $adjustments as $val ) {
  157. // Normalize argument
  158. $_val = $val ? trim( str_replace( '%', '', $val ) ) : 0;
  159. // Reduce value to float
  160. $_val /= 100;
  161. // Adjust alpha component if necessary
  162. if ( 3 === $index ) {
  163. if ( 0 != $val ) {
  164. $alpha = max( 0, min( 1, $alpha + $_val ) );
  165. }
  166. }
  167. // Adjust HSL component value if necessary
  168. else {
  169. if ( 0 != $val ) {
  170. $hsl[ $index ] = max( 0, min( 1, $hsl[ $index ] + $_val ) );
  171. }
  172. }
  173. $index++;
  174. }
  175. // Finally convert new HSL value to RGB
  176. $rgb = CssCrush_Color::hslToRgb( $hsl );
  177. // Return as hex if there is no modified alpha channel
  178. // Otherwise return RGBA string
  179. if ( 1 === $alpha ) {
  180. return CssCrush_Color::rgbToHex( $rgb );
  181. }
  182. $rgb[] = $alpha;
  183. return 'rgba(' . implode( ',', $rgb ) . ')';
  184. }
  185. else {
  186. return $color;
  187. }
  188. }
  189. ############
  190. public static function css_fn__math ( $input ) {
  191. // Whitelist allowed characters
  192. $input = preg_replace( '![^\.0-9\*\/\+\-\(\)]!', '', $input );
  193. $result = 0;
  194. try {
  195. $result = eval( "return $input;" );
  196. }
  197. catch ( Exception $e ) {};
  198. return round( $result, 10 );
  199. }
  200. public static function css_fn__percent ( $input ) {
  201. $args = self::parseMathArgs( $input );
  202. // Use precision argument if it exists, default to 7
  203. $precision = isset( $args[2] ) ? $args[2] : 7;
  204. $result = 0;
  205. if ( count( $args ) > 1 ) {
  206. // Arbitary high precision division
  207. $div = (string) bcdiv( $args[0], $args[1], 25 );
  208. // Set precision percentage value
  209. $result = (string) bcmul( $div, '100', $precision );
  210. // Trim unnecessary zeros and decimals
  211. $result = trim( $result, '0' );
  212. $result = rtrim( $result, '.' );
  213. }
  214. return $result . '%';
  215. }
  216. // Percent function alias
  217. public static function css_fn__pc ( $input ) {
  218. return self::css_fn_percent( $input );
  219. }
  220. public static function css_fn__data_uri ( $input ) {
  221. // Normalize, since argument might be a string token
  222. if ( strpos( $input, '___s' ) === 0 ) {
  223. $string_labels = array_keys( CssCrush::$storage->tokens->strings );
  224. $string_values = array_values( CssCrush::$storage->tokens->strings );
  225. $input = trim( str_replace( $string_labels, $string_values, $input ), '\'"`' );
  226. }
  227. // Default return value
  228. $result = "url($input)";
  229. // No attempt to process absolute urls
  230. if (
  231. strpos( $input, 'http://' ) === 0 or
  232. strpos( $input, 'https://' ) === 0
  233. ) {
  234. return $result;
  235. }
  236. // Get system file path
  237. if ( strpos( $input, '/' ) === 0 ) {
  238. $file = CssCrush::$config->docRoot . $input;
  239. }
  240. else {
  241. $baseDir = CssCrush::$config->baseDir;
  242. $file = "$baseDir/$input";
  243. }
  244. // File not found
  245. if ( !file_exists( $file ) ) {
  246. return $result;
  247. }
  248. $file_ext = pathinfo( $file, PATHINFO_EXTENSION );
  249. // Only allow certain extensions
  250. $allowed_file_extensions = array(
  251. 'woff' => 'font/woff;charset=utf-8',
  252. 'ttf' => 'font/truetype;charset=utf-8',
  253. 'svg' => 'image/svg+xml',
  254. 'svgz' => 'image/svg+xml',
  255. 'gif' => 'image/gif',
  256. 'jpeg' => 'image/jpg',
  257. 'jpg' => 'image/jpg',
  258. 'png' => 'image/png',
  259. );
  260. if ( !array_key_exists( $file_ext, $allowed_file_extensions ) ) {
  261. return $result;
  262. }
  263. $mime_type = $allowed_file_extensions[ $file_ext ];
  264. $base64 = base64_encode( file_get_contents( $file ) );
  265. $data_uri = "data:{$mime_type};base64,$base64";
  266. if ( strlen( $data_uri ) > 32000 ) {
  267. // Too big for IE
  268. }
  269. return "url($data_uri)";
  270. }
  271. public static function css_fn__h_adjust ( $input ) {
  272. @list( $color, $h ) = self::parseArgs( $input, true );
  273. return self::colorAdjust( $color, array( $h, 0, 0, 0 ) );
  274. }
  275. public static function css_fn__s_adjust ( $input ) {
  276. @list( $color, $s ) = self::parseArgs( $input, true );
  277. return self::colorAdjust( $color, array( 0, $s, 0, 0 ) );
  278. }
  279. public static function css_fn__l_adjust ( $input ) {
  280. @list( $color, $l ) = self::parseArgs( $input, true );
  281. return self::colorAdjust( $color, array( 0, 0, $l, 0 ) );
  282. }
  283. public static function css_fn__a_adjust ( $input ) {
  284. @list( $color, $a ) = self::parseArgs( $input, true );
  285. return self::colorAdjust( $color, array( 0, 0, 0, $a ) );
  286. }
  287. }