From a3f64a4214fe0ae29ecea298542a15c6a0071a45 Mon Sep 17 00:00:00 2001
From: "ctrlaltca@gmail.com" <>
Date: Sun, 25 Mar 2012 20:51:49 +0000
Subject: Reworked the patch for #391; now a TComponent-based controls can
contain methods prefixed by "js" to indicate that those methods can receive
raw javascript. Such methods can be called both in a xss-safe,
javascript-encoded way: $xxx->Property="yyy" and in a raw-javascript way:
$xxx->jsProperty="zzz". Patch by gabor, documentation is on the way
---
framework/TComponent.php | 98 ++++++++++++++++++++++++++---
framework/Web/Javascripts/TJavaScript.php | 22 +------
framework/Web/UI/TTemplateManager.php | 24 +++----
framework/Web/UI/WebControls/TAccordion.php | 20 +++---
framework/Web/UI/WebControls/TTabPanel.php | 18 +++---
5 files changed, 117 insertions(+), 65 deletions(-)
diff --git a/framework/TComponent.php b/framework/TComponent.php
index dcc1064c..e413aebc 100644
--- a/framework/TComponent.php
+++ b/framework/TComponent.php
@@ -84,6 +84,7 @@ class TComponent
* to allow using the following syntax to read a property:
*
* $value=$component->PropertyName;
+ * $value=$component->jsPropertyName; // return JavaScript literal
*
* and to obtain the event handler list for an event,
*
@@ -95,12 +96,17 @@ class TComponent
*/
public function __get($name)
{
- $getter='get'.$name;
+ $getter='get'.$name; $jsgetter = 'getjs'.$name;
if(method_exists($this,$getter))
{
// getting a property
return $this->$getter();
}
+ else if(method_exists($this,$jsgetter))
+ {
+ // getting a property
+ return (string)$this->$jsgetter();
+ }
else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
{
// getting an event (handler list)
@@ -121,6 +127,7 @@ class TComponent
* to allow using the following syntax to set a property or attach an event handler.
*
* $this->PropertyName=$value;
+ * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
* $this->EventName=$handler;
*
* @param string the property name or event name
@@ -129,16 +136,23 @@ class TComponent
*/
public function __set($name,$value)
{
- $setter='set'.$name;
- if(method_exists($this,$setter))
+ if(method_exists($this, $setter='set'.$name))
{
+ if (strncasecmp($name,'js',2)===0 && $value && !($value instanceof TJavaScriptLiteral))
+ $value = new TJavaScriptLiteral($value);
$this->$setter($value);
}
+ else if(method_exists($this, $jssetter = 'setjs'.$name))
+ {
+ if ($value and !($value instanceof TJavaScriptString))
+ $value = new TJavaScriptString($value);
+ $this->$jssetter($value);
+ }
else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
{
$this->attachEventHandler($name,$value);
}
- else if(method_exists($this,'get'.$name))
+ else if(method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name))
{
throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
}
@@ -148,6 +162,40 @@ class TComponent
}
}
+ /**
+ * Calls a method.
+ * Do not call this method. This is a PHP magic method that we override
+ * to allow using the following syntax to call a property setter or getter.
+ *
+ * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
+ * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
+ *
+ * @param string the getter or setter method name
+ * @param mixed method call parameters
+ * @throws TInvalidOperationException If the property is not defined or read-only.
+ */
+ public function __call($name,$params)
+ {
+ $getset = substr($name,0,3);
+ if (($getset=='get') || ($getset=='set'))
+ {
+ $propname = substr($name,3);
+ $jsmethod = $getset.'js'.$propname;
+ if (method_exists($this, $jsmethod))
+ {
+ if (count($params)>0)
+ if ($params[0] && !($params[0] instanceof TJavaScriptString))
+ $params[0] = new TJavaScriptString($params[0]);
+ return call_user_func_array(array($this, $jsmethod), $params);
+ }
+
+ if (($getset=='set') and method_exists($this, 'getjs'.$propname))
+ throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
+ }
+
+ throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
+ }
+
/**
* Determines whether a property is defined.
* A property is defined if there is a getter or setter method
@@ -157,7 +205,10 @@ class TComponent
*/
public function hasProperty($name)
{
- return method_exists($this,'get'.$name) || method_exists($this,'set'.$name);
+ return
+ method_exists($this,'get'.$name) || method_exists($this,'set'.$name) ||
+ method_exists($this,'getjs'.$name) || method_exists($this,'setjs'.$name)
+ ;
}
/**
@@ -169,7 +220,7 @@ class TComponent
*/
public function canGetProperty($name)
{
- return method_exists($this,'get'.$name);
+ return method_exists($this,'get'.$name) || method_exists($this,'getjs'.$name);
}
/**
@@ -181,7 +232,7 @@ class TComponent
*/
public function canSetProperty($name)
{
- return method_exists($this,'set'.$name);
+ return method_exists($this,'set'.$name) || method_exists($this,'setjs'.$name);
}
/**
@@ -901,3 +952,36 @@ class TComponentReflection extends TComponent
return $this->_methods;
}
}
+
+/**
+ * TJavaScriptLiteral class that encloses string literals that are not
+ * supposed to be escaped by TJavaScript::encode()
+ *
+ */
+class TJavaScriptLiteral
+{
+ protected $_s;
+
+ public function __construct($s)
+ {
+ $this->_s = $s;
+ }
+
+ public function __toString()
+ {
+ return (string)$this->_s;
+ }
+
+ public function toJavaScriptLiteral()
+ {
+ return $this->__toString();
+ }
+}
+
+class TJavaScriptString extends TJavaScriptLiteral
+{
+ public function toJavaScriptLiteral()
+ {
+ return TJavaScript::jsonEncode((string)$this->_s,JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG);
+ }
+}
\ No newline at end of file
diff --git a/framework/Web/Javascripts/TJavaScript.php b/framework/Web/Javascripts/TJavaScript.php
index 468182d2..8c7c865b 100644
--- a/framework/Web/Javascripts/TJavaScript.php
+++ b/framework/Web/Javascripts/TJavaScript.php
@@ -198,7 +198,7 @@ class TJavaScript
}
else if(is_object($value))
if ($value instanceof TJavaScriptLiteral)
- return preg_replace('/^\s*javascript:/', '', $value);
+ return $value->toJavaScriptLiteral();
else
return self::encode(get_object_vars($value),$toMap);
else if($value===null)
@@ -281,23 +281,3 @@ class TJavaScript
}
}
-/**
- * TJavaScriptLiteral class that encloses string literals that are not
- * supposed to be escaped by TJavaScript::encode()
- *
- */
-class TJavaScriptLiteral
-{
- private $_s;
-
- public function __construct($s)
- {
- $this->_s = $s;
- }
-
- public function __toString()
- {
- return $this->_s;
- }
-}
-
diff --git a/framework/Web/UI/TTemplateManager.php b/framework/Web/UI/TTemplateManager.php
index 566f6876..41ff76e1 100644
--- a/framework/Web/UI/TTemplateManager.php
+++ b/framework/Web/UI/TTemplateManager.php
@@ -423,17 +423,10 @@ class TTemplate extends TApplicationComponent implements ITemplate
{
if(strncasecmp($name,'on',2)===0) // is an event
$this->configureEvent($control,$name,$value,$control);
- else {
- if(strncasecmp($name,'js',2)===0)
- {
- $name=substr($name,2);
- $value=TJavaScript::quoteJsLiteral($value);
- }
- if(($pos=strrpos($name,'.'))===false) // is a simple property or custom attribute
- $this->configureProperty($control,$name,$value);
- else // is a subproperty
- $this->configureSubProperty($control,$name,$value);
- }
+ else if(($pos=strrpos($name,'.'))===false) // is a simple property or custom attribute
+ $this->configureProperty($control,$name,$value);
+ else // is a subproperty
+ $this->configureSubProperty($control,$name,$value);
}
/**
@@ -513,6 +506,9 @@ class TTemplate extends TApplicationComponent implements ITemplate
}
else
{
+ if (substr($name,0,2)=='js')
+ if ($value and !($value instanceof TJavaScriptLiteral))
+ $value = new TJavaScriptLiteral($value);
$setter='set'.$name;
$component->$setter($value);
}
@@ -944,12 +940,10 @@ class TTemplate extends TApplicationComponent implements ITemplate
}
else
{
- if(strncasecmp($name,'js',2)===0)
- $name=substr($name, 2);
// a simple property
- if(!$class->hasMethod('set'.$name))
+ if (! ($class->hasMethod('set'.$name) || $class->hasMethod('setjs'.$name)) )
{
- if($class->hasMethod('get'.$name))
+ if ($class->hasMethod('get'.$name) || $class->hasMethod('getjs'.$name))
throw new TConfigurationException('template_property_readonly',$type,$name);
else
throw new TConfigurationException('template_property_unknown',$type,$name);
diff --git a/framework/Web/UI/WebControls/TAccordion.php b/framework/Web/UI/WebControls/TAccordion.php
index 4e1cd325..8fd53e29 100644
--- a/framework/Web/UI/WebControls/TAccordion.php
+++ b/framework/Web/UI/WebControls/TAccordion.php
@@ -477,23 +477,17 @@ class TAccordion extends TWebControl implements IPostBackDataHandler
*/
protected function getClientOptions()
{
- $options['ID']=$this->getClientID();
- $options['ActiveHeaderCssClass']=$this->getActiveHeaderCssClass();
- $options['HeaderCssClass']=$this->getHeaderCssClass();
- $options['Duration']=$this->getAnimationDuration();
+ $options['ID'] = $this->getClientID();
+ $options['ActiveHeaderCssClass'] = $this->getActiveHeaderCssClass();
+ $options['HeaderCssClass'] = $this->getHeaderCssClass();
+ $options['Duration'] = $this->getAnimationDuration();
if (($viewheight = $this->getViewHeight())>0)
$options['maxHeight'] = $viewheight;
- $views='';
+ $views = array();
foreach($this->getViews() as $view)
- {
- if($views!='')
- $views.=', ';
- $views.= TJavaScript::encode($view->getClientID()).':'.($view->getVisible() ? '1': '0' );
- }
-
- $options['Views']=TJavaScript::quoteJsLiteral('{'.$views.='}');
- $viewIDs=array();
+ $views[$view->getClientID()] = $view->getVisible() ? '1': '0';
+ $options['Views'] = $views;
return $options;
}
diff --git a/framework/Web/UI/WebControls/TTabPanel.php b/framework/Web/UI/WebControls/TTabPanel.php
index 558ead4e..7f0c2d36 100644
--- a/framework/Web/UI/WebControls/TTabPanel.php
+++ b/framework/Web/UI/WebControls/TTabPanel.php
@@ -436,18 +436,18 @@ class TTabPanel extends TWebControl implements IPostBackDataHandler
*/
protected function getClientOptions()
{
- $options['ID']=$this->getClientID();
- $options['ActiveCssClass']=$this->getActiveTabCssClass();
- $options['NormalCssClass']=$this->getTabCssClass();
- $viewIDs=array();
- $viewVis=array();
+ $options['ID'] = $this->getClientID();
+ $options['ActiveCssClass'] = $this->getActiveTabCssClass();
+ $options['NormalCssClass'] = $this->getTabCssClass();
+ $viewIDs = array();
+ $viewVis = array();
foreach($this->getViews() as $view)
{
- $viewIDs[]=TJavaScript::encode($view->getClientID());
- $viewVis[]=TJavaScript::encode($view->getVisible());
+ $viewIDs[] = $view->getClientID();
+ $viewVis[] = $view->getVisible();
}
- $options['Views']=TJavaScript::quoteJsLiteral('['.implode(',',$viewIDs).']');
- $options['ViewsVis']=TJavaScript::quoteJsLiteral('['.implode(',',$viewVis).']');
+ $options['Views'] = $viewIDs;
+ $options['ViewsVis'] = $viewVis;
return $options;
}
--
cgit v1.2.3