Skip to content

Commit

Permalink
Refactoring out string escaping and object conversion into base class.
Browse files Browse the repository at this point in the history
  • Loading branch information
markstory committed Mar 8, 2009
1 parent 93a2e8f commit 2362f28
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 91 deletions.
184 changes: 124 additions & 60 deletions cake/libs/view/helpers/js.php
Expand Up @@ -350,23 +350,6 @@ function load_($url = null, $options = array()) {
function redirect_($url = null) {
return 'window.location = "' . Router::url($url) . '";';
}
/**
* Escape a string to be JavaScript friendly.
*
* List of escaped ellements:
* + "\r\n" => '\n'
* + "\r" => '\n'
* + "\n" => '\n'
* + '"' => '\"'
* + "'" => "\\'"
*
* @param string $script String that needs to get escaped.
* @return string Escaped string.
*/
function escape($string) {
$escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
return str_replace(array_keys($escape), array_values($escape), $string);
}

/* function get__($name) {
return $this->__object($name, 'id');
Expand All @@ -387,76 +370,157 @@ function __object($name, $var) {
}
return $this->__objects[$name];
}


}

/**
* JsEngineBaseClass
*
* Abstract Base Class for All JsEngines to extend. Provides generic methods.
*
* @package cake.view.helpers
*/
class JsBaseEngineHelper extends AppHelper {
/**
* Determines whether native JSON extension is used for encoding. Set by object constructor.
*
* @var boolean
* @access public
*/
var $useNative = false;
/**
* Constructor.
*
* @return void
**/
function __construct() {
$this->useNative = function_exists('json_encode');
}
/**
* Generates a JavaScript object in JavaScript Object Notation (JSON)
* from an array
*
* @param array $data Data to be converted
* @param boolean $block Wraps return value in a <script/> block if true
* @param string $prefix Prepends the string to the returned data
* @param string $postfix Appends the string to the returned data
* @param array $stringKeys A list of array keys to be treated as a string
* @param boolean $quoteKeys If false, treats $stringKey as a list of keys *not* to be quoted
* @param string $q The type of quote to use
* Options:
* - prefix - String prepended to the returned data.
* - postfix - String appended to the returned data.
* - stringKeys - A list of array keys to be treated as a string
* - quoteKeys - If false treats $options['stringKeys'] as a list of keys **not** to be quoted.
* - q - Type of quote to use.
*
* @param array $data Data to be converted.
* @param array $options Set of options, see above.
* @return string A JSON code block
*/
function object($data = array(), $block = false, $prefix = '', $postfix = '', $stringKeys = array(), $quoteKeys = true, $q = "\"") {
function object($data = array(), $options = array()) {
$defaultOptions = array(
'block' => false, 'prefix' => '', 'postfix' => '',
'stringKeys' => array(), 'quoteKeys' => true, 'q' => '"'
);
$options = array_merge($defaultOptions, $options);

if (is_object($data)) {
$data = get_object_vars($data);
}

$out = array();
$key = array();

if (is_array($data)) {
$keys = array_keys($data);
}

$out = $keys = array();
$numeric = true;

if (!empty($keys)) {
foreach ($keys as $key) {
if (!is_numeric($key)) {
$numeric = false;
break;
}
if ($this->useNative) {
$rt = json_encode($data);
} else {
if (is_array($data)) {
$keys = array_keys($data);
}
}

foreach ($data as $key => $val) {
if (is_array($val) || is_object($val)) {
$val = $this->object($val, false, '', '', $stringKeys, $quoteKeys, $q);
} else {
if ((!count($stringKeys) && !is_numeric($val) && !is_bool($val)) || ($quoteKeys && in_array($key, $stringKeys)) || (!$quoteKeys && !in_array($key, $stringKeys)) && $val !== null) {
$val = $q . $this->escapeString($val) . $q;
if (!empty($keys)) {
$numeric = (array_values($keys) === array_keys(array_values($keys)));
}

foreach ($data as $key => $val) {
if (is_array($val) || is_object($val)) {
$val = $this->object($val, array_merge($options, array('block' => false)));
} else {
$quoteStrings = (
!count($options['stringKeys']) ||
($options['quoteKeys'] && in_array($key, $options['stringKeys'], true)) ||
(!$options['quoteKeys'] && !in_array($key, $options['stringKeys'], true))
);
$val = $this->value($val, $quoteStrings);
}
if ($val == null) {
$val = 'null';
if (!$numeric) {
$val = $options['q'] . $this->value($key, false) . $options['q'] . ':' . $val;
}
$out[] = $val;
}

if (!$numeric) {
$val = $q . $key . $q . ':' . $val;
$rt = '{' . join(',', $out) . '}';
} else {
$rt = '[' . join(',', $out) . ']';
}

$out[] = $val;
}
$rt = $options['prefix'] . $rt . $options['postfix'];

if (!$numeric) {
$rt = '{' . join(', ', $out) . '}';
} else {
$rt = '[' . join(', ', $out) . ']';
if ($options['block']) {
$rt = $this->codeBlock($rt, array_diff_key($options, $defaultOptions));
}
$rt = $prefix . $rt . $postfix;

if ($block) {
$rt = $this->codeBlock($rt);
}

return $rt;
}
/**
* Converts a PHP-native variable of any type to a JSON-equivalent representation
*
* @param mixed $val A PHP variable to be converted to JSON
* @param boolean $quoteStrings If false, leaves string values unquoted
* @return string a JavaScript-safe/JSON representation of $val
*/
function value($val, $quoteStrings = true) {
switch (true) {
case (is_array($val) || is_object($val)):
$val = $this->object($val);
break;
case ($val === null):
$val = 'null';
break;
case (is_bool($val)):
$val = ($val === true) ? 'true' : 'false';
break;
case (is_int($val)):
$val = $val;
break;
case (is_float($val)):
$val = sprintf("%.11f", $val);
break;
default:
$val = $this->escape($val);
if ($quoteStrings) {
$val = '"' . $val . '"';
}
break;
}
return $val;
}
/**
* Escape a string to be JavaScript friendly.
*
* List of escaped ellements:
* + "\r\n" => '\n'
* + "\r" => '\n'
* + "\n" => '\n'
* + '"' => '\"'
* + "'" => "\\'"
*
* @param string $script String that needs to get escaped.
* @return string Escaped string.
*/
function escape($string) {
$escape = array("\r\n" => '\n', "\r" => '\n', "\n" => '\n', '"' => '\"', "'" => "\\'");
return str_replace(array_keys($escape), array_values($escape), $string);
}

}


class JsHelperObject {
var $__parent = null;

Expand Down
90 changes: 59 additions & 31 deletions cake/tests/cases/libs/view/helpers/js.test.php
Expand Up @@ -44,7 +44,8 @@ class JsHelperTestCase extends CakeTestCase {
* @return void
*/
function startTest() {
$this->Js = new JsHelper();
$this->Js = new JsHelper('JsBase');
$this->Js->JsBaseEngine = new JsBaseEngineHelper();
}
/**
* tearDown method
Expand Down Expand Up @@ -90,36 +91,6 @@ function testMethodDispatching() {
$this->expectError();
$js->someMethodThatSurelyDoesntExist();
}
/**
* test escape string skills
*
* @return void
**/
function testEscaping() {
$result = $this->Js->escape('');
$expected = '';
$this->assertEqual($result, $expected);

$result = $this->Js->escape('CakePHP' . "\n" . 'Rapid Development Framework');
$expected = 'CakePHP\\nRapid Development Framework';
$this->assertEqual($result, $expected);

$result = $this->Js->escape('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP');
$expected = 'CakePHP\\nRapid Development Framework\\nFor PHP';
$this->assertEqual($result, $expected);

$result = $this->Js->escape('CakePHP: "Rapid Development Framework"');
$expected = 'CakePHP: \\"Rapid Development Framework\\"';
$this->assertEqual($result, $expected);

$result = $this->Js->escape('CakePHP: \'Rapid Development Framework\'');
$expected = 'CakePHP: \\\'Rapid Development Framework\\\'';
$this->assertEqual($result, $expected);

$result = $this->Js->escape('my \\"string\\"');
$expected = 'my \\\"string\\\"';
$this->assertEqual($result, $expected);
}
/**
* test prompt() creation
*
Expand Down Expand Up @@ -273,4 +244,61 @@ function testRedirect() {
}
}

/**
* JsBaseEngine Class Test case
*
* @package cake.tests.view.helpers
**/
class JsBaseEngineTestCase extends CakeTestCase {
/**
* setUp method
*
* @access public
* @return void
*/
function startTest() {
$this->JsEngine = new JsBaseEngineHelper();
}
/**
* tearDown method
*
* @access public
* @return void
*/
function endTest() {
ClassRegistry::removeObject('view');
unset($this->JsEngine);
}
/**
* test escape string skills
*
* @return void
**/
function testEscaping() {
$result = $this->JsEngine->escape('');
$expected = '';
$this->assertEqual($result, $expected);

$result = $this->JsEngine->escape('CakePHP' . "\n" . 'Rapid Development Framework');
$expected = 'CakePHP\\nRapid Development Framework';
$this->assertEqual($result, $expected);

$result = $this->JsEngine->escape('CakePHP' . "\r\n" . 'Rapid Development Framework' . "\r" . 'For PHP');
$expected = 'CakePHP\\nRapid Development Framework\\nFor PHP';
$this->assertEqual($result, $expected);

$result = $this->JsEngine->escape('CakePHP: "Rapid Development Framework"');
$expected = 'CakePHP: \\"Rapid Development Framework\\"';
$this->assertEqual($result, $expected);

$result = $this->JsEngine->escape('CakePHP: \'Rapid Development Framework\'');
$expected = 'CakePHP: \\\'Rapid Development Framework\\\'';
$this->assertEqual($result, $expected);

$result = $this->JsEngine->escape('my \\"string\\"');
$expected = 'my \\\"string\\\"';
$this->assertEqual($result, $expected);
}
}

?>

0 comments on commit 2362f28

Please sign in to comment.