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