Skip to content
This repository
Browse code

Refactoring out string escaping and object conversion into base class.

  • Loading branch information...
commit 2362f283f2773e27ada02ad7a79af18c8a3b9077 1 parent 93a2e8f
Mark Story authored March 07, 2009
184  cake/libs/view/helpers/js.php
@@ -350,23 +350,6 @@ function load_($url = null, $options = array()) {
350 350
 	function redirect_($url = null) {
351 351
 		return 'window.location = "' . Router::url($url) . '";';
352 352
 	}
353  
-/**
354  
- * Escape a string to be JavaScript friendly.
355  
- *
356  
- * List of escaped ellements:
357  
- *	+ "\r\n" => '\n'
358  
- *	+ "\r" => '\n'
359  
- *	+ "\n" => '\n'
360  
- *	+ '"' => '\"'
361  
- *	+ "'" => "\\'"
362  
- *
363  
- * @param  string $script String that needs to get escaped.
364  
- * @return string Escaped string.
365  
- */
366  
-	function escape($string) {
367  
-		$escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
368  
-		return str_replace(array_keys($escape), array_values($escape), $string);
369  
-	}
370 353
 
371 354
 /*	function get__($name) {
372 355
 		return $this->__object($name, 'id');
@@ -387,76 +370,157 @@ function __object($name, $var) {
387 370
 		}
388 371
 		return $this->__objects[$name];
389 372
 	}
  373
+
  374
+
  375
+}
  376
+
  377
+/**
  378
+ * JsEngineBaseClass 
  379
+ * 
  380
+ * Abstract Base Class for All JsEngines to extend. Provides generic methods.
  381
+ *
  382
+ * @package cake.view.helpers
  383
+ */
  384
+class JsBaseEngineHelper extends AppHelper {
  385
+/**
  386
+ * Determines whether native JSON extension is used for encoding.  Set by object constructor.
  387
+ *
  388
+ * @var boolean
  389
+ * @access public
  390
+ */
  391
+	var $useNative = false;
  392
+/**
  393
+ * Constructor.
  394
+ *
  395
+ * @return void
  396
+ **/
  397
+	function __construct() {
  398
+		$this->useNative = function_exists('json_encode');
  399
+	}
390 400
 /**
391 401
  * Generates a JavaScript object in JavaScript Object Notation (JSON)
392 402
  * from an array
393 403
  *
394  
- * @param array $data Data to be converted
395  
- * @param boolean $block Wraps return value in a <script/> block if true
396  
- * @param string $prefix Prepends the string to the returned data
397  
- * @param string $postfix Appends the string to the returned data
398  
- * @param array $stringKeys A list of array keys to be treated as a string
399  
- * @param boolean $quoteKeys If false, treats $stringKey as a list of keys *not* to be quoted
400  
- * @param string $q The type of quote to use
  404
+ * Options:
  405
+ *  - prefix - String prepended to the returned data.
  406
+ *  - postfix - String appended to the returned data.
  407
+ *  - stringKeys - A list of array keys to be treated as a string
  408
+ *  - quoteKeys - If false treats $options['stringKeys'] as a list of keys **not** to be quoted.
  409
+ *  - q - Type of quote to use.
  410
+ * 
  411
+ * @param array $data Data to be converted.
  412
+ * @param array $options Set of options, see above.
401 413
  * @return string A JSON code block
402 414
  */
403  
-	function object($data = array(), $block = false, $prefix = '', $postfix = '', $stringKeys = array(), $quoteKeys = true, $q = "\"") {
  415
+	function object($data = array(), $options = array()) {
  416
+		$defaultOptions = array(
  417
+			'block' => false, 'prefix' => '', 'postfix' => '',
  418
+			'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"'
  419
+		);
  420
+		$options = array_merge($defaultOptions, $options);
  421
+
404 422
 		if (is_object($data)) {
405 423
 			$data = get_object_vars($data);
406 424
 		}
407 425
 
408  
-		$out = array();
409  
-		$key = array();
410  
-
411  
-		if (is_array($data)) {
412  
-			$keys = array_keys($data);
413  
-		}
414  
-
  426
+		$out = $keys = array();
415 427
 		$numeric = true;
416 428
 
417  
-		if (!empty($keys)) {
418  
-			foreach ($keys as $key) {
419  
-				if (!is_numeric($key)) {
420  
-					$numeric = false;
421  
-					break;
422  
-				}
  429
+		if ($this->useNative) {
  430
+			$rt = json_encode($data);
  431
+		} else {
  432
+			if (is_array($data)) {
  433
+				$keys = array_keys($data);
423 434
 			}
424  
-		}
425 435
 
426  
-		foreach ($data as $key => $val) {
427  
-			if (is_array($val) || is_object($val)) {
428  
-				$val = $this->object($val, false, '', '', $stringKeys, $quoteKeys, $q);
429  
-			} else {
430  
-				if ((!count($stringKeys) && !is_numeric($val) && !is_bool($val)) || ($quoteKeys && in_array($key, $stringKeys)) || (!$quoteKeys && !in_array($key, $stringKeys)) && $val !== null) {
431  
-					$val = $q . $this->escapeString($val) . $q;
  436
+			if (!empty($keys)) {
  437
+				$numeric = (array_values($keys) === array_keys(array_values($keys)));
  438
+			}
  439
+
  440
+			foreach ($data as $key => $val) {
  441
+				if (is_array($val) || is_object($val)) {
  442
+					$val = $this->object($val, array_merge($options, array('block' => false)));
  443
+				} else {
  444
+					$quoteStrings = (
  445
+						!count($options['stringKeys']) ||
  446
+						($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) ||
  447
+						(!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))
  448
+					);
  449
+					$val = $this->value($val, $quoteStrings);
432 450
 				}
433  
-				if ($val == null) {
434  
-					$val = 'null';
  451
+				if (!$numeric) {
  452
+					$val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
435 453
 				}
  454
+				$out[] = $val;
436 455
 			}
437 456
 
438 457
 			if (!$numeric) {
439  
-				$val = $q . $key . $q . ':' . $val;
  458
+				$rt = '{' . join(',', $out) . '}';
  459
+			} else {
  460
+				$rt = '[' . join(',', $out) . ']';
440 461
 			}
441  
-
442  
-			$out[] = $val;
443 462
 		}
  463
+		$rt = $options['prefix'] . $rt . $options['postfix'];
444 464
 
445  
-		if (!$numeric) {
446  
-			$rt = '{' . join(', ', $out) . '}';
447  
-		} else {
448  
-			$rt = '[' . join(', ', $out) . ']';
  465
+		if ($options['block']) {
  466
+			$rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
449 467
 		}
450  
-		$rt = $prefix . $rt . $postfix;
451  
-
452  
-		if ($block) {
453  
-			$rt = $this->codeBlock($rt);
454  
-		}
455  
-
456 468
 		return $rt;
457 469
 	}
  470
+/**
  471
+ * Converts a PHP-native variable of any type to a JSON-equivalent representation
  472
+ *
  473
+ * @param mixed $val A PHP variable to be converted to JSON
  474
+ * @param boolean $quoteStrings If false, leaves string values unquoted
  475
+ * @return string a JavaScript-safe/JSON representation of $val
  476
+ */
  477
+	function value($val, $quoteStrings = true) {
  478
+		switch (true) {
  479
+			case (is_array($val) || is_object($val)):
  480
+				$val = $this->object($val);
  481
+			break;
  482
+			case ($val === null):
  483
+				$val = 'null';
  484
+			break;
  485
+			case (is_bool($val)):
  486
+				$val = ($val === true) ? 'true' : 'false';
  487
+			break;
  488
+			case (is_int($val)):
  489
+				$val = $val;
  490
+			break;
  491
+			case (is_float($val)):
  492
+				$val = sprintf("%.11f", $val);
  493
+			break;
  494
+			default:
  495
+				$val = $this->escape($val);
  496
+				if ($quoteStrings) {
  497
+					$val = '"' . $val . '"';
  498
+				}
  499
+			break;
  500
+		}
  501
+		return $val;
  502
+	}
  503
+/**
  504
+ * Escape a string to be JavaScript friendly.
  505
+ *
  506
+ * List of escaped ellements:
  507
+ *	+ "\r\n" => '\n'
  508
+ *	+ "\r" => '\n'
  509
+ *	+ "\n" => '\n'
  510
+ *	+ '"' => '\"'
  511
+ *	+ "'" => "\\'"
  512
+ *
  513
+ * @param  string $script String that needs to get escaped.
  514
+ * @return string Escaped string.
  515
+ */
  516
+	function escape($string) {
  517
+		$escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
  518
+		return str_replace(array_keys($escape), array_values($escape), $string);
  519
+	}
  520
+
458 521
 }
459 522
 
  523
+
460 524
 class JsHelperObject {
461 525
 	var $__parent = null;
462 526
 
90  cake/tests/cases/libs/view/helpers/js.test.php
@@ -44,7 +44,8 @@ class JsHelperTestCase extends CakeTestCase {
44 44
  * @return void
45 45
  */
46 46
 	function startTest() {
47  
-		$this->Js = new JsHelper();
  47
+		$this->Js = new JsHelper('JsBase');
  48
+		$this->Js->JsBaseEngine = new JsBaseEngineHelper();
48 49
 	}
49 50
 /**
50 51
  * tearDown method
@@ -91,36 +92,6 @@ function testMethodDispatching() {
91 92
 		$js->someMethodThatSurelyDoesntExist();
92 93
 	}
93 94
 /**
94  
- * test escape string skills
95  
- *
96  
- * @return void
97  
- **/
98  
-	function testEscaping() {
99  
-		$result = $this->Js->escape('');
100  
-		$expected = '';
101  
-		$this->assertEqual($result, $expected);
102  
-
103  
-		$result = $this->Js->escape('CakePHP' . "\n" . 'Rapid Development Framework');
104  
-		$expected = 'CakePHP\\nRapid Development Framework';
105  
-		$this->assertEqual($result, $expected);
106  
-
107  
-		$result = $this->Js->escape('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP');
108  
-		$expected = 'CakePHP\\nRapid Development Framework\\nFor PHP';
109  
-		$this->assertEqual($result, $expected);
110  
-
111  
-		$result = $this->Js->escape('CakePHP: "Rapid Development Framework"');
112  
-		$expected = 'CakePHP: \\"Rapid Development Framework\\"';
113  
-		$this->assertEqual($result, $expected);
114  
-
115  
-		$result = $this->Js->escape('CakePHP: \'Rapid Development Framework\'');
116  
-		$expected = 'CakePHP: \\\'Rapid Development Framework\\\'';
117  
-		$this->assertEqual($result, $expected);
118  
-
119  
-		$result = $this->Js->escape('my \\"string\\"');
120  
-		$expected = 'my \\\"string\\\"';
121  
-		$this->assertEqual($result, $expected);
122  
-	}
123  
-/**
124 95
  * test prompt() creation
125 96
  *
126 97
  * @return void
@@ -273,4 +244,61 @@ function testRedirect() {
273 244
 	}
274 245
 }
275 246
 
  247
+/**
  248
+ * JsBaseEngine Class Test case
  249
+ *
  250
+ * @package cake.tests.view.helpers
  251
+ **/
  252
+class JsBaseEngineTestCase extends CakeTestCase {
  253
+/**
  254
+ * setUp method
  255
+ *
  256
+ * @access public
  257
+ * @return void
  258
+ */
  259
+	function startTest() {
  260
+		$this->JsEngine = new JsBaseEngineHelper();
  261
+	}
  262
+/**
  263
+ * tearDown method
  264
+ *
  265
+ * @access public
  266
+ * @return void
  267
+ */
  268
+	function endTest() {
  269
+		ClassRegistry::removeObject('view');
  270
+		unset($this->JsEngine);
  271
+	}
  272
+/**
  273
+ * test escape string skills
  274
+ *
  275
+ * @return void
  276
+ **/
  277
+	function testEscaping() {
  278
+		$result = $this->JsEngine->escape('');
  279
+		$expected = '';
  280
+		$this->assertEqual($result, $expected);
  281
+
  282
+		$result = $this->JsEngine->escape('CakePHP' . "\n" . 'Rapid Development Framework');
  283
+		$expected = 'CakePHP\\nRapid Development Framework';
  284
+		$this->assertEqual($result, $expected);
  285
+
  286
+		$result = $this->JsEngine->escape('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP');
  287
+		$expected = 'CakePHP\\nRapid Development Framework\\nFor PHP';
  288
+		$this->assertEqual($result, $expected);
  289
+
  290
+		$result = $this->JsEngine->escape('CakePHP: "Rapid Development Framework"');
  291
+		$expected = 'CakePHP: \\"Rapid Development Framework\\"';
  292
+		$this->assertEqual($result, $expected);
  293
+
  294
+		$result = $this->JsEngine->escape('CakePHP: \'Rapid Development Framework\'');
  295
+		$expected = 'CakePHP: \\\'Rapid Development Framework\\\'';
  296
+		$this->assertEqual($result, $expected);
  297
+
  298
+		$result = $this->JsEngine->escape('my \\"string\\"');
  299
+		$expected = 'my \\\"string\\\"';
  300
+		$this->assertEqual($result, $expected);
  301
+	}
  302
+}
  303
+
276 304
 ?>

0 notes on commit 2362f28

Please sign in to comment.
Something went wrong with that request. Please try again.