@@ -6,15 +6,14 @@ abstract class Component{
66 private static $ isTagsSet = false ; //flag to track if all html tag classes created
77
88 //all html tags that are allowed
9- private static $ htmlTags = ['div ' ,'p ' ,'img ' ,'a ' ,'ul ' ,'li ' , 'h1 ' ,'h2 ' ,'h3 ' ,'h4 ' ,'h5 ' ,'h6 ' ,'iframe ' ,'article ' , 'form ' ,'input ' ,'textarea ' ,'select ' ,'option ' , 'link ' , 'script ' , 'button ' , 'nav ' , 'title ' , 'meta ' , 'code ' , 'pre ' , ' abbr ' , ' svg ' , ' g ' , ' path ' , ' image ' , ' circle ' ];
9+ private static $ htmlTags = ['div ' ,'p ' ,'img ' ,'a ' ,'ul ' ,'li ' , 'h1 ' ,'h2 ' ,'h3 ' ,'h4 ' ,'h5 ' ,'h6 ' ,'iframe ' ,'article ' , 'form ' ,'input ' ,'textarea ' ,'select ' ,'option ' , 'link ' , 'script ' , 'button ' , 'nav ' , 'title ' , 'meta ' , 'code ' , 'pre ' ];
1010
1111 private static $ hasNoChild = ['img ' , 'link ' , 'input ' , 'meta ' ]; //tags that have no children
1212 private const tagNameSpace = 'React\Tag ' ; //name space for the tags
1313
14- private static $ counter = 1 ; // couter for generating sequencial id
14+ private static $ counter = 1 ; // counter for generating sequencial id
1515 protected $ id = '' ; //the current id of the component
1616 protected $ state = []; //the current state
17- private static $ states = []; //used to save all states of every component in the page
1817
1918 /*
2019 run the first time when first component called
@@ -30,7 +29,7 @@ private function setTags(){
3029 self ::$ isTagsSet = true ;
3130
3231 //script tag to setup setState function
33- echo new \React \Tag \script ('const phpReact={setState: function(t,e,n ){var a=document.getElementById(t );if(a ){var r=this.getState(t );"function"==typeof e &&(e=e(r ));var o =new XMLHttpRequest;o .onreadystatechange=function(){4==this.readyState&&200==this.status&&(a .outerHTML=this.responseText," function"==typeof n&& n())},o .open("POST",location.href,!0),o .setRequestHeader("Content-type","application/x-www-form-urlencoded"),o .send("phpreact="+JSON.stringify({id:t,state:e,prevState:r} ))}},getState: function(t ){try{var e=document.getElementById(t );return JSON.parse(e .getAttribute("prevstate "))}catch(t){return{}}}} ; ' );
32+ echo new \React \Tag \script ('!function(t,e){var n=function(){e.querySelectorAll("[component-id] *:not([component-id])").forEach( function(t){t.setState||(t.getState=i,t.setState=o)})},o=function(t,o ){var i=this.closest("[component-id]" );if(i ){var a=i.getAttribute("component-id"), r=this.getAttribute("key"),c=this.value,s=this. getState();"function"==typeof t &&(t=t(s ));var u =new XMLHttpRequest,p={id:a,state:t,prevState:s};u .onreadystatechange=function(){if( 4==this.readyState&&200==this.status){if(i .outerHTML=this.responseText,r){var t=e.querySelector("[component-id= ' "+a+ " '] [key=' " +r+ " '] " );t&&(t. focus (),c&&(t.value="",t.value=c))}" function "==typeof o&& o (), n ()}},u .open ("POST " ,location.href,!0 ),u .setRequestHeader ("Content-type " ,"application/x-www-form-urlencoded " ),u .send ("phpreact= " +JSON .stringify (p ))}},i= function (){try {var t= this . closest ( " [component-id] " );return JSON .parse (t .getAttribute ("component-state " ))}catch (t ){return {}}};t. addEventListener ( " load " ,n)}(window,document) ;');
3433 }
3534
3635 /*
@@ -60,7 +59,7 @@ protected function hasNoChild(){
6059 @param: $hasNoChild: bool if the tags accept no children
6160 */
6261 static function registerTag ($ tags , $ hasNoChild = false ){
63- self ::$ htmlTags = array_unique (array_merge (self ::$ htmlTags , ( array ) $ tags ));
62+ self ::$ htmlTags = array_unique (array_merge (self ::$ htmlTags , self :: parseTags ( $ tags) ));
6463 if ($ hasNoChild ) $ this ->setHasNoChild ($ tags );
6564 }
6665
@@ -69,18 +68,31 @@ static function registerTag($tags, $hasNoChild = false){
6968 @param: $tag: string|array[list of string] html tags
7069 */
7170 static function setHasNoChild ($ tags ){
72- self ::$ hasNoChild = array_unique (array_merge (self ::$ hasNoChild , (array )$ tags ));
71+ self ::$ hasNoChild = array_unique (array_merge (self ::$ hasNoChild , self ::parseTags ($ tags )));
72+ }
73+
74+ /*
75+ @param: $tags: string|array -- array string to be parse
76+ @return: parsed array string
77+ */
78+ private static function parseTags ($ tags ){
79+ return array_map (function ($ tag ){ return self ::parseAttribute ($ tag ); }, (array )$ tags );
80+ }
81+
82+ /*
83+ allow only [words or dash] for attribute or tag
84+ @param: $attr: string -- the string to be parse
85+ @return: parsed string
86+ */
87+ private static function parseAttribute ($ attr ){
88+ return preg_replace ('/[^\w-]/ ' ,'' , $ attr ); //allow only [words or dash]
7389 }
7490
7591 /*
7692 render the html tag only
7793 */
7894 function render (){
7995 if (!$ this ->isHtmlTage ()) return '' ;
80-
81- //save states in dom attribute [prevstate]
82- if ($ this ->props ->id && self ::$ states [$ this ->props ->id ])
83- $ this ->props ->prevstate = json_encode (self ::$ states [$ this ->props ->id ]);
8496
8597 $ tag = $ this ->getTagName ();
8698 $ innerHtml = '' ;
@@ -89,11 +101,12 @@ function render(){
89101 if ($ k == 'dangerouslyInnerHTML ' ){ //if has dangerouslyInnerHTML attribute
90102 $ innerHtml = $ v ; continue ;
91103 }
92- $ att = preg_replace ( ' /[^\w-]/ ' , '' , $ k ); //allow only [words or dash]
93- $ val = htmlspecialchars ($ v ); //escape html
104+ $ att = self :: parseAttribute ( $ k ); //allow only [words or dash]
105+ $ val = htmlspecialchars ( is_object ( $ v ) || is_array ( $ v ) ? json_encode ( $ v ) : $ v ); //escape html
94106
95- $ attr [] = "$ att=' $ v ' " ;
107+ $ attr [] = "$ att=' $ val ' " ;
96108 }
109+
97110 $ attributes = implode (' ' ,$ attr );
98111
99112 //if theres innerHtml then ignore children else escape any string passed as html
@@ -110,6 +123,12 @@ function render(){
110123 */
111124 function __toString (){
112125 $ components = $ this ->render ();
126+
127+ //save state of custom component in top html wrapper
128+ if (!$ this ->isHtmlTage () && $ components instanceof Component && $ components ->isHtmlTage ()){
129+ $ components ->props = (object )array_merge ((array )$ components ->props , ['component-id ' => $ this ->id , 'component-state ' => $ this ->state ]);
130+ }
131+
113132 if (!is_array ($ components )) $ components = [$ components ]; //must be list of components
114133
115134 //if custom component the render should return component or list of components
@@ -155,7 +174,6 @@ function componentDidUpdate($oldState, $currentState){}
155174 private function setId (){
156175 if ($ this ->isHtmlTage ()) return ;
157176 $ this ->id = md5 (self ::$ counter ); //generate id
158- self ::$ states [$ this ->id ] = $ this ->state ; //save all states by id
159177 self ::$ counter ++;
160178 }
161179
@@ -169,7 +187,6 @@ private function setStateListener(){
169187 if (!$ post || $ post ->id != $ this ->id ) return ;
170188 $ oldState = $ post ->prevState ;
171189 $ this ->state = (object )array_merge ((array )$ oldState , (array )$ post ->state );
172- self ::$ states [$ this ->id ] = $ this ->state ;
173190 $ this ->componentDidUpdate ($ oldState , $ this ->state );
174191 @ob_end_clean ();
175192 die ($ this );
0 commit comments