MediaWiki  1.23.8
CSSJanus.php
Go to the documentation of this file.
1 <?php
37 class CSSJanus {
38  // Patterns defined as null are built dynamically by buildPatterns()
39  private static $patterns = array(
40  'tmpToken' => '`TMP`',
41  'nonAscii' => '[\200-\377]',
42  'unicode' => '(?:(?:\\[0-9a-f]{1,6})(?:\r\n|\s)?)',
43  'num' => '(?:[0-9]*\.[0-9]+|[0-9]+)',
44  'unit' => '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)',
45  'body_selector' => 'body\s*{\s*',
46  'direction' => 'direction\s*:\s*',
47  'escape' => null,
48  'nmstart' => null,
49  'nmchar' => null,
50  'ident' => null,
51  'quantity' => null,
52  'possibly_negative_quantity' => null,
53  'color' => null,
54  'url_special_chars' => '[!#$%&*-~]',
55  'valid_after_uri_chars' => '[\'\"]?\s*',
56  'url_chars' => null,
57  'lookahead_not_open_brace' => null,
58  'lookahead_not_closing_paren' => null,
59  'lookahead_for_closing_paren' => null,
60  'lookahead_not_letter' => '(?![a-zA-Z])',
61  'lookbehind_not_letter' => '(?<![a-zA-Z])',
62  'chars_within_selector' => '[^\}]*?',
63  'noflip_annotation' => '\/\*\s*@noflip\s*\*\/',
64  'noflip_single' => null,
65  'noflip_class' => null,
66  'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//',
67  'direction_ltr' => null,
68  'direction_rtl' => null,
69  'left' => null,
70  'right' => null,
71  'left_in_url' => null,
72  'right_in_url' => null,
73  'ltr_in_url' => null,
74  'rtl_in_url' => null,
75  'cursor_east' => null,
76  'cursor_west' => null,
77  'four_notation_quantity' => null,
78  'four_notation_color' => null,
79  'border_radius' => null,
80  'box_shadow' => null,
81  'text_shadow1' => null,
82  'text_shadow2' => null,
83  'bg_horizontal_percentage' => null,
84  'bg_horizontal_percentage_x' => null,
85  );
86 
90  private static function buildPatterns() {
91  if ( !is_null( self::$patterns['escape'] ) ) {
92  // Patterns have already been built
93  return;
94  }
95 
97  $patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])";
98  $patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})";
99  $patterns['nmchar'] = "(?:[_a-z0-9-]|{$patterns['nonAscii']}|{$patterns['escape']})";
100  $patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*";
101  $patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?";
102  $patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))";
103  $patterns['color'] = "(#?{$patterns['nmchar']}+|(?:rgba?|hsla?)\([ \d.,%-]+\))";
104  $patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*";
105  $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>|\(|\)|\[|\]|=|\*=|~=|\^=|'[^']*'])*?{)";
106  $patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
107  $patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
108  $patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i";
109  $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i";
110  $patterns['direction_ltr'] = "/({$patterns['direction']})ltr/i";
111  $patterns['direction_rtl'] = "/({$patterns['direction']})rtl/i";
112  $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
113  $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
114  $patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i";
115  $patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i";
116  $patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i";
117  $patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i";
118  $patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/";
119  $patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/";
120  $patterns['four_notation_quantity'] = "/(:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
121  $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s*[;}])/i";
122  $patterns['border_radius'] = "/(border-radius\s*:\s*){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*[;}])/i";
123  $patterns['box_shadow'] = "/(box-shadow\s*:\s*(?:inset\s*)?){$patterns['possibly_negative_quantity']}/i";
124  $patterns['text_shadow1'] = "/(text-shadow\s*:\s*){$patterns['color']}(\s*){$patterns['possibly_negative_quantity']}/i";
125  $patterns['text_shadow2'] = "/(text-shadow\s*:\s*){$patterns['possibly_negative_quantity']}/i";
126  // The two regexes below are parenthesized differently then in the original implementation to make the
127  // callback's job more straightforward
128  $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)(-?{$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
129  $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)(-?{$patterns['num']})(%)/";
130  }
131 
139  public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) {
140  // We wrap tokens in ` , not ~ like the original implementation does.
141  // This was done because ` is not a legal character in CSS and can only
142  // occur in URLs, where we escape it to %60 before inserting our tokens.
143  $css = str_replace( '`', '%60', $css );
144 
146 
147  // Tokenize single line rules with /* @noflip */
148  $noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' );
149  $css = $noFlipSingle->tokenize( $css );
150 
151  // Tokenize class rules with /* @noflip */
152  $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' );
153  $css = $noFlipClass->tokenize( $css );
154 
155  // Tokenize comments
156  $comments = new CSSJanus_Tokenizer( self::$patterns['comment'], '`C`' );
157  $css = $comments->tokenize( $css );
158 
159  // LTR->RTL fixes start here
161  if ( $swapLtrRtlInURL ) {
163  }
164 
165  if ( $swapLeftRightInURL ) {
167  }
174 
175  // Detokenize stuff we tokenized before
176  $css = $comments->detokenize( $css );
177  $css = $noFlipClass->detokenize( $css );
178  $css = $noFlipSingle->detokenize( $css );
179 
180  return $css;
181  }
182 
195  private static function fixDirection( $css ) {
196  $css = preg_replace( self::$patterns['direction_ltr'],
197  '$1' . self::$patterns['tmpToken'], $css );
198  $css = preg_replace( self::$patterns['direction_rtl'], '$1ltr', $css );
199  $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css );
200 
201  return $css;
202  }
203 
209  private static function fixLtrRtlInURL( $css ) {
210  $css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css );
211  $css = preg_replace( self::$patterns['rtl_in_url'], 'ltr', $css );
212  $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css );
213 
214  return $css;
215  }
216 
222  private static function fixLeftRightInURL( $css ) {
223  $css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css );
224  $css = preg_replace( self::$patterns['right_in_url'], 'left', $css );
225  $css = str_replace( self::$patterns['tmpToken'], 'right', $css );
226 
227  return $css;
228  }
229 
235  private static function fixLeftAndRight( $css ) {
236  $css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css );
237  $css = preg_replace( self::$patterns['right'], 'left', $css );
238  $css = str_replace( self::$patterns['tmpToken'], 'right', $css );
239 
240  return $css;
241  }
242 
248  private static function fixCursorProperties( $css ) {
249  $css = preg_replace( self::$patterns['cursor_east'],
250  '$1' . self::$patterns['tmpToken'], $css );
251  $css = preg_replace( self::$patterns['cursor_west'], '$1e-resize', $css );
252  $css = str_replace( self::$patterns['tmpToken'], 'w-resize', $css );
253 
254  return $css;
255  }
256 
269  private static function fixFourPartNotation( $css ) {
270  $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$3$8$5$6$7$4$9', $css );
271  $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4$9', $css );
272  return $css;
273  }
274 
282  private static function fixBorderRadius( $css ) {
283  // Undo four_notation_quantity
284  $css = preg_replace( self::$patterns['border_radius'], '$1$2$3$8$5$6$7$4$9', $css );
285  // Do the real thing
286  $css = preg_replace( self::$patterns['border_radius'], '$1$4$3$2$5$8$7$6$9', $css );
287 
288  return $css;
289  }
290 
297  private static function fixShadows( $css ) {
298  // Flips the sign of a CSS value, possibly with a unit.
299  // (We can't just negate the value with unary minus due to the units.)
300  $flipSign = function ( $cssValue ) {
301  // Don't mangle zeroes
302  if ( intval( $cssValue ) === 0 ) {
303  return $cssValue;
304  } elseif ( $cssValue[0] === '-' ) {
305  return substr( $cssValue, 1 );
306  } else {
307  return "-" . $cssValue;
308  }
309  };
310 
311  $css = preg_replace_callback( self::$patterns['box_shadow'], function ( $matches ) use ( $flipSign ) {
312  return $matches[1] . $flipSign( $matches[2] );
313  }, $css );
314 
315  $css = preg_replace_callback( self::$patterns['text_shadow1'], function ( $matches ) use ( $flipSign ) {
316  return $matches[1] . $matches[2] . $matches[3] . $flipSign( $matches[4] );
317  }, $css );
318 
319  $css = preg_replace_callback( self::$patterns['text_shadow2'], function ( $matches ) use ( $flipSign ) {
320  return $matches[1] . $flipSign( $matches[2] );
321  }, $css );
322 
323  return $css;
324  }
325 
331  private static function fixBackgroundPosition( $css ) {
332  $replaced = preg_replace_callback( self::$patterns['bg_horizontal_percentage'],
333  array( 'self', 'calculateNewBackgroundPosition' ), $css );
334  if ( $replaced !== null ) {
335  // Check for null; sometimes preg_replace_callback() returns null here for some weird reason
336  $css = $replaced;
337  }
338  $replaced = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'],
339  array( 'self', 'calculateNewBackgroundPosition' ), $css );
340  if ( $replaced !== null ) {
341  $css = $replaced;
342  }
343 
344  return $css;
345  }
346 
352  private static function calculateNewBackgroundPosition( $matches ) {
353  return $matches[1] . ( 100 - $matches[2] ) . $matches[3];
354  }
355 }
356 
363  private $regex, $token;
364  private $originals;
365 
371  public function __construct( $regex, $token ) {
372  $this->regex = $regex;
373  $this->token = $token;
374  $this->originals = array();
375  }
376 
383  public function tokenize( $str ) {
384  return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str );
385  }
386 
391  private function tokenizeCallback( $matches ) {
392  $this->originals[] = $matches[0];
393  return $this->token;
394  }
395 
402  public function detokenize( $str ) {
403  // PHP has no function to replace only the first occurrence or to
404  // replace occurrences of the same string with different values,
405  // so we use preg_replace_callback() even though we don't really need a regex
406  return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/',
407  array( $this, 'detokenizeCallback' ), $str );
408  }
409 
414  private function detokenizeCallback( $matches ) {
415  $retval = current( $this->originals );
416  next( $this->originals );
417 
418  return $retval;
419  }
420 }
CSSJanus\buildPatterns
static buildPatterns()
Build patterns we can't define above because they depend on other patterns.
Definition: CSSJanus.php:90
CSSJanus_Tokenizer\$originals
$originals
Definition: CSSJanus.php:364
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
CSSJanus_Tokenizer\detokenize
detokenize( $str)
Replace tokens with their originals.
Definition: CSSJanus.php:402
token
as a message key or array as accepted by ApiBase::dieUsageMsg after processing request parameters Return false to let the request returning an error message or an< edit result="Failure"> tag if $resultArr was filled which will be used in the intoken parameter and in the and a callback function which should return the token
Definition: hooks.txt:421
CSSJanus\fixLtrRtlInURL
static fixLtrRtlInURL( $css)
Replace 'ltr' with 'rtl' and vice versa in background URLs.
Definition: CSSJanus.php:209
CSSJanus\fixLeftRightInURL
static fixLeftRightInURL( $css)
Replace 'left' with 'right' and vice versa in background URLs.
Definition: CSSJanus.php:222
CSSJanus_Tokenizer\tokenize
tokenize( $str)
Replace all occurrences of $regex in $str with a token and remember the original strings.
Definition: CSSJanus.php:383
CSSJanus\$patterns
static $patterns
Definition: CSSJanus.php:39
$css
$css
Definition: styleTest.css.php:50
CSSJanus\fixDirection
static fixDirection( $css)
Replace direction: ltr; with direction: rtl; and vice versa.
Definition: CSSJanus.php:195
CSSJanus\fixFourPartNotation
static fixFourPartNotation( $css)
Swap the second and fourth parts in four-part notation rules like padding: 1px 2px 3px 4px;.
Definition: CSSJanus.php:269
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
CSSJanus\fixShadows
static fixShadows( $css)
Negates horizontal offset in box-shadow and text-shadow rules.
Definition: CSSJanus.php:297
CSSJanus_Tokenizer\$token
$token
Definition: CSSJanus.php:363
CSSJanus\fixBackgroundPosition
static fixBackgroundPosition( $css)
Flip horizontal background percentages.
Definition: CSSJanus.php:331
$matches
if(!defined( 'MEDIAWIKI')) if(!isset( $wgVersion)) $matches
Definition: NoLocalSettings.php:33
CSSJanus\calculateNewBackgroundPosition
static calculateNewBackgroundPosition( $matches)
Callback for calculateNewBackgroundPosition()
Definition: CSSJanus.php:352
CSSJanus_Tokenizer
Utility class used by CSSJanus that tokenizes and untokenizes things we want to protect from being ja...
Definition: CSSJanus.php:362
CSSJanus_Tokenizer\tokenizeCallback
tokenizeCallback( $matches)
Definition: CSSJanus.php:391
CSSJanus\fixCursorProperties
static fixCursorProperties( $css)
Flip East and West in rules like cursor: nw-resize;.
Definition: CSSJanus.php:248
CSSJanus\fixBorderRadius
static fixBorderRadius( $css)
Swaps appropriate corners in four-part border-radius rules.
Definition: CSSJanus.php:282
CSSJanus\fixLeftAndRight
static fixLeftAndRight( $css)
Flip rules like left: , padding-right: , etc.
Definition: CSSJanus.php:235
CSSJanus\transform
static transform( $css, $swapLtrRtlInURL=false, $swapLeftRightInURL=false)
Transform an LTR stylesheet to RTL.
Definition: CSSJanus.php:139
CSSJanus_Tokenizer\$regex
$regex
Definition: CSSJanus.php:363
CSSJanus
This is a PHP port of CSSJanus, a utility that transforms CSS style sheets written for LTR to RTL.
Definition: CSSJanus.php:37
$retval
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account incomplete not yet checked for validity & $retval
Definition: hooks.txt:237
CSSJanus_Tokenizer\detokenizeCallback
detokenizeCallback( $matches)
Definition: CSSJanus.php:414
CSSJanus_Tokenizer\__construct
__construct( $regex, $token)
Constructor.
Definition: CSSJanus.php:371