]> git.sur5r.net Git - bacula/bacula/blob - gui/baculum/framework/TComponent.php
baculum: Tweak escape config file values
[bacula/bacula] / gui / baculum / framework / TComponent.php
1 <?php
2 /**
3  * TComponent, TPropertyValue classes
4  *
5  * @author Qiang Xue <qiang.xue@gmail.com>
6  *
7  * Global Events, intra-object events, Class behaviors, expanded behaviors
8  * @author Brad Anderson <javalizard@mac.com>
9  *
10  * @link http://www.pradosoft.com/
11  * @copyright Copyright &copy; 2005-2014 PradoSoft
12  * @license http://www.pradosoft.com/license/
13  * @package System
14  */
15
16 /**
17  * TComponent class
18  *
19  * TComponent is the base class for all PRADO components.
20  * TComponent implements the protocol of defining, using properties, behaviors,
21  * and events.
22  *
23  * A property is defined by a getter method, and/or a setter method.
24  * Properties can be accessed in the way like accessing normal object members.
25  * Reading or writing a property will cause the invocation of the corresponding
26  * getter or setter method, e.g.,
27  * <code>
28  * $a=$this->Text;     // equivalent to $a=$this->getText();
29  * $this->Text='abc';  // equivalent to $this->setText('abc');
30  * </code>
31  * The signatures of getter and setter methods are as follows,
32  * <code>
33  * // getter, defines a readable property 'Text'
34  * function getText() { ... }
35  * // setter, defines a writable property 'Text', with $value being the value to be set to the property
36  * function setText($value) { ... }
37  * </code>
38  * Property names are case-insensitive. It is recommended that they are written
39  * in the format of concatenated words, with the first letter of each word
40  * capitalized (e.g. DisplayMode, ItemStyle).
41  *
42  * Javascript Get and Set
43  *
44  * Since Prado 3.2 a new class of javascript-friendly properties have been introduced
45  * to better deal with potential security problems like cross-site scripting issues.
46  * All the data that gets sent clientside inside a javascript block is now encoded by default.
47  * Sometimes there's the need to bypass this encoding and be able to send raw javascript code.
48  * This new class of javascript-friendly properties are identified by their name
49  * starting with 'js' (case insensitive):
50  * <code>
51  * // getter, defines a readable property 'Text'
52  * function getJsText() { ... }
53  * // setter, defines a writable property 'Text', with $value being the value to be set to the property
54  * function setJsText(TJavaScriptLiteral $value) { ... }
55  * </code>
56  * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
57  * <code>
58  * // set some simple text as property value
59  * $component->Text = 'text';
60  * // set some javascript code as property value
61  * $component->JsText = 'raw javascript';
62  * </code>
63  * In the first case, the property value will automatically gets encoded when sent
64  * clientside inside a javascript block.
65  * In the second case, the property will be 'marked' as being a safe javascript
66  * statement and will not be encoded when rendered inside a javascript block.
67  * This special handling makes use of the {@link TJavaScriptLiteral} class.
68  *
69  * Events
70  *
71  * An event is defined by the presence of a method whose name starts with 'on'.
72  * The event name is the method name and is thus case-insensitive.
73  * An event can be attached with one or several methods (called event handlers).
74  * An event can be raised by calling {@link raiseEvent} method, upon which
75  * the attached event handlers will be invoked automatically in the order they
76  * are attached to the event. Event handlers must have the following signature,
77  * <code>
78  * function eventHandlerFuncName($sender,$param) { ... }
79  * </code>
80  * where $sender refers to the object who is responsible for the raising of the event,
81  * and $param refers to a structure that may contain event-specific information.
82  * To raise an event (assuming named as 'Click') of a component, use
83  * <code>
84  * $component->raiseEvent('OnClick');
85  * $component->raiseEvent('OnClick', $this, $param);
86  * </code>
87  * To attach an event handler to an event, use one of the following ways,
88  * <code>
89  * $component->OnClick=$callback;  // or $component->OnClick->add($callback);
90  * $component->attachEventHandler('OnClick',$callback);
91  * </code>
92  * The first two ways make use of the fact that $component->OnClick refers to
93  * the event handler list {@link TPriorityList} for the 'OnClick' event.
94  * The variable $callback contains the definition of the event handler that can
95  * be either a string referring to a global function name, or an array whose
96  * first element refers to an object and second element a method name/path that
97  * is reachable by the object, e.g.
98  * - 'buttonClicked' : buttonClicked($sender,$param);
99  * - array($object,'buttonClicked') : $object->buttonClicked($sender,$param);
100  * - array($object,'MainContent.SubmitButton.buttonClicked') :
101  *   $object->MainContent->SubmitButton->buttonClicked($sender,$param);
102  *
103  * With the addition of behaviors, a more expansive event model is needed.  There
104  * are two new event types (global and dynamic events) as well as a more comprehensive
105  * behavior model that includes class wide behaviors.
106  *
107  * A global event is defined by all events whose name starts with 'fx'.
108  * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
109  * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
110  * any global event by defining that event as a method. Global events have priorities
111  * just like 'on' events; so as to be able to order the event execution. Due to the
112  * nature of all events which start with 'fx' being valid, in effect, every object
113  * has every 'fx' global event. It is simply an issue of tapping into the desired
114  * global event.
115  *
116  * A global event that starts with 'fx' can be called even if the object does not
117  * implement the method of the global event.  A call to a non-existing 'fx' method
118  * will, at minimal, function and return null.  If a method argument list has a first
119  * parameter, it will be returned instead of null.  This allows filtering and chaining.
120  * 'fx' methods do not automatically install and uninstall. To install and uninstall an
121  * object's global event listeners, call the object's {@link listen} and
122  * {@link unlisten} methods, respectively.  An object may auto-install its global event
123  * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
124  *
125  * As of PHP version 5.3, nulled objects without code references will still continue to persist
126  * in the global event queue because {@link __destruct} is not automatically called.  In the common
127  * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
128  * {@link unlisten} is required to be manually called before an object is
129  * left without references if it is currently listening to any global events. This includes
130  * class wide behaviors.
131  *
132  * An object that contains a method that starts with 'fx' will have those functions
133  * automatically receive those events of the same name after {@link listen} is called on the object.
134  *
135  * An object may listen to a global event without defining an 'fx' method of the same name by
136  * adding an object method to the global event list.  For example
137  * <code>
138  * $component->fxGlobalCheck=$callback;  // or $component->OnClick->add($callback);
139  * $component->attachEventHandler('fxGlobalCheck',array($object, 'someMethod'));
140  * </code>
141  *
142  * Events between Objects and their behaviors, Dynamic Events
143  *
144  * An intra-object/behavior event is defined by methods that start with 'dy'.  Just as with
145  * 'fx' global events, every object has every dynamic event.  Any call to a method that
146  * starts with 'dy' will be handled, regardless of whether it is implemented.  These
147  * events are for communicating with attached behaviors.
148  *
149  * Dynamic events can be used in a variety of ways.  They can be used to tell behaviors
150  * when a non-behavior method is called.  Dynamic events could be used as data filters.
151  * They could also be used to specify when a piece of code is to be run, eg. should the
152  * loop process be performed on a particular piece of data.  In this way, some control
153  * is handed to the behaviors over the process and/or data.
154  *
155  * If there are no handlers for an 'fx' or 'dy' event, it will return the first
156  * parameter of the argument list.  If there are no arguments, these events
157  * will return null.  If there are handlers an 'fx' method will be called directly
158  * within the object.  Global 'fx' events are triggered by calling {@link raiseEvent}.
159  * For dynamic events where there are behaviors that respond to the dynamic events, a
160  * {@link TCallChain} is developed.  A call chain allows the behavior dynamic event
161  * implementations to call further implementing behaviors within a chain.
162  *
163  * If an object implements {@link IDynamicMethods}, all global and object dynamic
164  * events will be sent to {@link __dycall}.  In the case of global events, all
165  * global events will trigger this method.  In the case of behaviors, all undefined
166  * dynamic events  which are called will be passed through to this method.
167  *
168  *
169  * Behaviors
170  *
171  * There are two types of behaviors.  There are individual object behaviors and
172  * there are class wide behaviors.  Class behaviors depend upon object behaviors.
173  *
174  * When a new class implements {@link IBehavior} or {@link IClassBehavior} or
175  * extends {@link TBehavior} or {@link TClassBehavior}, it may be added to an
176  * object by calling the object's {@link attachBehavior}.  The behaviors associated
177  * name can then be used to {@link enableBehavior} or {@link disableBehavior}
178  * the specific behavior.
179  *
180  * All behaviors may be turned on and off via {@link enableBehaviors} and
181  * {@link disableBehaviors}, respectively.  To check if behaviors are on or off
182  * a call to {@link getBehaviorsEnabled} will provide the variable.
183  *
184  * Attaching and detaching whole sets of behaviors is done using
185  * {@link attachBehaviors} and {@link detachBehaviors}.  {@link clearBehaviors}
186  * removes all of an object's behaviors.
187  *
188  * {@link asa} returns a behavior of a specific name.  {@link isa} is the
189  * behavior inclusive function that acts as the PHP operator {@link instanceof}.
190  * A behavior could provide the functionality of a specific class thus causing
191  * the host object to act similarly to a completely different class.  A behavior
192  * would then implement {@link IInstanceCheck} to provide the identity of the
193  * different class.
194  *
195  * Class behaviors are similar to object behaviors except that the class behavior
196  * is the implementation for all instances of the class.  A class behavior
197  * will have the object upon which is being called be prepended to the parameter
198  * list.  This way the object is known across the class behavior implementation.
199  *
200  * Class behaviors are attached using {@link attachClassBehavior} and detached
201  * using {@link detachClassBehavior}.  Class behaviors are important in that
202  * they will be applied to all new instances of a particular class.  In this way
203  * class behaviors become default behaviors to a new instances of a class in
204  * {@link __construct}.  Detaching a class behavior will remove the behavior
205  * from the default set of behaviors created for an object when the object
206  * is instanced.
207  *
208  * Class behaviors are also added to all existing instances via the global 'fx'
209  * event mechanism.  When a new class behavior is added, the event
210  * {@link fxAttachClassBehavior} is raised and all existing instances that are
211  * listening to this global event (primarily after {@link listen} is called)
212  * will have this new behavior attached.  A similar process is used when
213  * detaching class behaviors.  Any objects listening to the global 'fx' event
214  * {@link fxDetachClassBehavior} will have a class behavior removed.
215  *
216  * Dynamic Intra-Object Events
217  *
218  * Dynamic events start with 'dy'.  This mechanism is used to allow objects
219  * to communicate with their behaviors directly.  The entire 'dy' event space
220  * is valid.  All attached, enabled behaviors that implement a dynamic event
221  * are called when the host object calls the dynamic event.  If there is no
222  * implementation or behaviors, this returns null when no parameters are
223  * supplied and will return the first parameter when there is at least one
224  * parameter in the dynamic event.
225  * <code>
226  *       null == $this->dyBehaviorEvent();
227  *       5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
228  * </code>
229  *
230  * Dynamic events can be chained together within behaviors to allow for data
231  * filtering. Dynamic events are implemented within behaviors by defining the
232  * event as a method.
233  * <code>
234  * class TObjectBehavior extends TBehavior {
235  *     public function dyBehaviorEvent($param1, $callchain) {
236  *                      //Do something, eg:  $param1 += 13;
237  *                      return $callchain->dyBehaviorEvent($param1);
238  *     }
239  * }
240  * </code>
241  * This implementation of a behavior and dynamic event will flow through to the
242  * next behavior implementing the dynamic event.  The first parameter is always
243  * return when it is supplied.  Otherwise a dynamic event returns null.
244  *
245  * In the case of a class behavior, the object is also prepended to the dynamic
246  * event.
247  * <code>
248  * class TObjectClassBehavior extends TClassBehavior {
249  *     public function dyBehaviorEvent($hostobject, $param1, $callchain) {
250  *                      //Do something, eg:  $param1 += $hostobject->getNumber();
251  *                      return $callchain->dyBehaviorEvent($param1);
252  *     }
253  * }
254  * </code>
255  * When calling a dynamic event, only the parameters are passed.  The host object
256  * and the call chain are built into the framework.
257  *
258  * Global Event and Dynamic event catching
259  *
260  * Given that all global 'fx' events and dynamic 'dy' events are valid and
261  * operational, there is a mechanism for catching events called that are not
262  * implemented (similar to the built-in PHP method {@link __call}).  When
263  * a dynamic or global event is called but a behavior does not implement it,
264  * yet desires to know when an undefined dynamic event is run, the behavior
265  * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
266  *
267  * In the case of dynamic events, {@link __dycall} is supplied with the method
268  * name and its parameters.  When a global event is raised, via {@link raiseEvent},
269  * the method is the event name and the parameters are supplied.
270  *
271  * When implemented, this catch-all mechanism is called for event global event event
272  * when implemented outside of a behavior.  Within a behavior, it will also be called
273  * when the object to which the behavior is attached calls any unimplemented dynamic
274  * event.  This is the fall-back mechanism for informing a class and/or behavior
275  * of when an global and/or undefined dynamic event is executed.
276  *
277  * @author Qiang Xue <qiang.xue@gmail.com>
278  * @author Brad Anderson <javalizard@mac.com>
279  * @package System
280  * @since 3.0
281  */
282 class TComponent
283 {
284         /**
285          * @var array event handler lists
286          */
287         private $_e=array();
288
289         /**
290          * @var boolean if listening is enabled.  Automatically turned on or off in
291          * constructor according to {@link getAutoGlobalListen}.  Default false, off
292          */
293         private $_listeningenabled=false;
294
295         /**
296          * @var array static registered global event handler lists
297          */
298         private static $_ue=array();
299
300         /**
301          * @var boolean if object behaviors are on or off.  default true, on
302          */
303         private $_behaviorsenabled=true;
304
305         /**
306          * @var TPriorityMap list of object behaviors
307          */
308         private $_m=null;
309
310         /**
311          * @var array static global class behaviors, these behaviors are added upon instantiation of a class
312          */
313         private static $_um=array();
314
315
316         /**
317          * @const string the name of the global {@link raiseEvent} listener
318          */
319         const GLOBAL_RAISE_EVENT_LISTENER='fxGlobalListener';
320
321
322         /**
323          * The common __construct
324          * If desired by the new object, this will auto install and listen to global event functions
325          * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
326          * This function installs all class behaviors in a class hierarchy from the deepest subclass
327          * through each parent to the top most class, TComponent.
328          */
329         public function __construct() {
330                 if($this->getAutoGlobalListen())
331                         $this->listen();
332
333                 $classes=array_reverse($this->getClassHierarchy(true));
334                 foreach($classes as $class) {
335                         if(isset(self::$_um[$class]))
336                                 $this->attachBehaviors(self::$_um[$class]);
337                 }
338         }
339
340
341         /**
342          * Tells TComponent whether or not to automatically listen to global events.
343          * Defaults to false because PHP variable cleanup is affected if this is true.
344          * When unsetting a variable that is listening to global events, {@link unlisten}
345          * must explicitly be called when cleaning variables allocation or else the global
346          * event registry will contain references to the old object. This is true for PHP 5.4
347          *
348          * Override this method by a subclass to change the setting.  When set to true, this
349          * will enable {@link __construct} to call {@link listen}.
350          *
351          * @return boolean whether or not to auto listen to global events during {@link __construct}, default false
352          */
353         public function getAutoGlobalListen() {
354                 return false;
355         }
356
357
358         /**
359          * The common __destruct
360          * This unlistens from the global event routines if listening
361          *
362          * PHP 5.3 does not __destruct objects when they are nulled and thus unlisten must be
363          * called must be explicitly called.
364          */
365         public function __destruct() {
366                 if($this->_listeningenabled)
367                         $this->unlisten();
368         }
369
370
371         /**
372          * This utility function is a private array filter method.  The array values
373          * that start with 'fx' are filtered in.
374          */
375         private function filter_prado_fx($name) {
376                 return strncasecmp($name,'fx',2)===0;
377         }
378
379
380         /**
381          * This returns an array of the class name and the names of all its parents.  The base object first,
382          * {@link TComponent}, and the deepest subclass is last.
383          * @param boolean optional should the names be all lowercase true/false
384          * @return array array of strings being the class hierarchy of $this.
385          */
386         public function getClassHierarchy($lowercase = false)
387         {
388                 $class=get_class($this);
389                 $classes=array($class);
390                 while($class=get_parent_class($class)){array_unshift($classes,$class);}
391                 if($lowercase)
392                         return array_map('strtolower',$classes);
393                 return $classes;
394         }
395
396
397         /**
398          * This adds an object's fx event handlers into the global broadcaster to listen into any
399          * broadcast global events called through {@link raiseEvent}
400          *
401          * Behaviors may implement the function:
402          * <code>
403          *      public function dyListen($globalEvents[, $chain]) {
404          *              $this->listen(); //eg
405          * }
406          * </code>
407          * to be executed when listen is called.  All attached behaviors are notified through dyListen.
408          *
409          * @return numeric the number of global events that were registered to the global event registry
410          */
411         public function listen() {
412                 if($this->_listeningenabled)
413                         return;
414
415                 $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
416
417                 foreach($fx as $func)
418                         $this->attachEventHandler($func,array($this,$func));
419
420                 if(is_a($this,'IDynamicMethods')) {
421                         $this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
422                         array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
423                 }
424
425                 $this->_listeningenabled=true;
426
427                 $this->dyListen($fx);
428
429                 return count($fx);
430         }
431
432         /**
433          * this removes an object's fx events from the global broadcaster
434          *
435          * Behaviors may implement the function:
436          * <code>
437          *      public function dyUnlisten($globalEvents[, $chain]) {
438          *              $this->behaviorUnlisten(); //eg
439          * }
440          * </code>
441          * to be executed when listen is called.  All attached behaviors are notified through dyUnlisten.
442          *
443          * @return numeric the number of global events that were unregistered from the global event registry
444          */
445         public function unlisten() {
446                 if(!$this->_listeningenabled)
447                         return;
448
449                 $fx=array_filter(get_class_methods($this),array($this,'filter_prado_fx'));
450
451                 foreach($fx as $func)
452                         $this->detachEventHandler($func,array($this,$func));
453
454                 if(is_a($this,'IDynamicMethods')) {
455                         $this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER,array($this,'__dycall'));
456                         array_push($fx,TComponent::GLOBAL_RAISE_EVENT_LISTENER);
457                 }
458
459                 $this->_listeningenabled=false;
460
461                 $this->dyUnlisten($fx);
462
463                 return count($fx);
464         }
465
466         /**
467          * Gets the state of listening to global events
468          * @return boolean is Listening to global broadcast enabled
469          */
470         public function getListeningToGlobalEvents()
471         {
472                 return $this->_listeningenabled;
473         }
474
475
476         /**
477          * Calls a method.
478          * Do not call this method directly. This is a PHP magic method that we override
479          * to allow behaviors, dynamic events (intra-object/behavior events),
480          * undefined dynamic and global events, and
481          * to allow using the following syntax to call a property setter or getter.
482          * <code>
483          * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
484          * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
485          * </code>
486          *
487          * Additional object behaviors override class behaviors.
488          * dynamic and global events do not fail even if they aren't implemented.
489          * Any intra-object/behavior dynamic events that are not implemented by the behavior
490          * return the first function paramater or null when no parameters are specified.
491          *
492          * @param string method name that doesn't exist and is being called on the object
493          * @param mixed method parameters
494          * @throws TInvalidOperationException If the property is not defined or read-only or
495          *              method is undefined
496          * @return mixed result of the method call, or false if 'fx' or 'dy' function but
497          *              is not found in the class, otherwise it runs
498          */
499         public function __call($method, $args)
500         {
501                 $getset=substr($method,0,3);
502                 if(($getset=='get')||($getset=='set'))
503                 {
504                         $propname=substr($method,3);
505                         $jsmethod=$getset.'js'.$propname;
506                         if(method_exists($this,$jsmethod))
507                         {
508                                 if(count($args)>0)
509                                         if($args[0]&&!($args[0] instanceof TJavaScriptString))
510                                                 $args[0]=new TJavaScriptString($args[0]);
511                                 return call_user_func_array(array($this,$jsmethod),$args);
512                         }
513
514                         if (($getset=='set')&&method_exists($this,'getjs'.$propname))
515                                 throw new TInvalidOperationException('component_property_readonly',get_class($this),$method);
516                 }
517
518                 if($this->_m!==null&&$this->_behaviorsenabled)
519                 {
520                         if(strncasecmp($method,'dy',2)===0)
521                         {
522                                 $callchain=new TCallChain($method);
523                                 foreach($this->_m->toArray() as $behavior)
524                                 {
525                                         if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&(method_exists($behavior,$method)||($behavior instanceof IDynamicMethods)))
526                                         {
527                                                 $behavior_args=$args;
528                                                 if($behavior instanceof IClassBehavior)
529                                                         array_unshift($behavior_args,$this);
530                                                 $callchain->addCall(array($behavior,$method),$behavior_args);
531                                         }
532
533                                 }
534                                 if($callchain->getCount()>0)
535                                         return call_user_func_array(array($callchain,'call'),$args);
536                         }
537                         else
538                         {
539                                 foreach($this->_m->toArray() as $behavior)
540                                 {
541                                         if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&method_exists($behavior,$method))
542                                         {
543                                                 if($behavior instanceof IClassBehavior)
544                                                         array_unshift($args,$this);
545                                                 return call_user_func_array(array($behavior,$method),$args);
546                                         }
547                                 }
548                         }
549                 }
550
551                 if(strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
552                 {
553                         if($this instanceof IDynamicMethods)
554                                 return $this->__dycall($method,$args);
555                         return isset($args[0])?$args[0]:null;
556                 }
557
558                 throw new TApplicationException('component_method_undefined',get_class($this),$method);
559         }
560
561
562         /**
563          * Returns a property value or an event handler list by property or event name.
564          * Do not call this method. This is a PHP magic method that we override
565          * to allow using the following syntax to read a property:
566          * <code>
567          * $value=$component->PropertyName;
568          * $value=$component->jsPropertyName; // return JavaScript literal
569          * </code>
570          * and to obtain the event handler list for an event,
571          * <code>
572          * $eventHandlerList=$component->EventName;
573          * </code>
574          * This will also return the global event handler list when specifing an 'fx'
575          * event,
576          * <code>
577          * $globalEventHandlerList=$component->fxEventName;
578          * </code>
579          * When behaviors are enabled, this will return the behavior of a specific
580          * name, a property of a behavior, or an object 'on' event defined by the behavior.
581          * @param string the property name or the event name
582          * @return mixed the property value or the event handler list as {@link TPriorityList}
583          * @throws TInvalidOperationException if the property/event is not defined.
584          */
585         public function __get($name)
586         {
587                 if(method_exists($this,$getter='get'.$name))
588                 {
589                         // getting a property
590                         return $this->$getter();
591                 }
592                 else if(method_exists($this,$jsgetter='getjs'.$name))
593                 {
594                         // getting a javascript property
595                         return (string)$this->$jsgetter();
596                 }
597                 else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
598                 {
599                         // getting an event (handler list)
600                         $name=strtolower($name);
601                         if(!isset($this->_e[$name]))
602                                 $this->_e[$name]=new TPriorityList;
603                         return $this->_e[$name];
604                 }
605                 else if(strncasecmp($name,'fx',2)===0)
606                 {
607                         // getting a global event (handler list)
608                         $name=strtolower($name);
609                         if(!isset(self::$_ue[$name]))
610                                 self::$_ue[$name]=new TPriorityList;
611                         return self::$_ue[$name];
612                 }
613                 else if($this->_behaviorsenabled)
614                 {
615                         // getting a behavior property/event (handler list)
616                         if(isset($this->_m[$name]))
617                                 return $this->_m[$name];
618                         else if($this->_m!==null)
619                         {
620                                 foreach($this->_m->toArray() as $behavior)
621                                 {
622                                         if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
623                                                 (property_exists($behavior,$name)||$behavior->canGetProperty($name)||$behavior->hasEvent($name)))
624                                                 return $behavior->$name;
625                                 }
626                         }
627                 }
628                 throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
629         }
630
631         /**
632          * Sets value of a component property.
633          * Do not call this method. This is a PHP magic method that we override
634          * to allow using the following syntax to set a property or attach an event handler.
635          * <code>
636          * $this->PropertyName=$value;
637          * $this->jsPropertyName=$value; // $value will be treated as a JavaScript literal
638          * $this->EventName=$handler;
639          * $this->fxEventName=$handler; //global event listener
640          * </code>
641          * When behaviors are enabled, this will also set a behaviors properties and events.
642          * @param string the property name or event name
643          * @param mixed the property value or event handler
644          * @throws TInvalidOperationException If the property is not defined or read-only.
645          */
646         public function __set($name,$value)
647         {
648                 if(method_exists($this,$setter='set'.$name))
649                 {
650                         if(strncasecmp($name,'js',2)===0&&$value&&!($value instanceof TJavaScriptLiteral))
651                                 $value = new TJavaScriptLiteral($value);
652                         return $this->$setter($value);
653                 }
654                 else if(method_exists($this,$jssetter='setjs'.$name))
655                 {
656                         if($value&&!($value instanceof TJavaScriptString))
657                                 $value=new TJavaScriptString($value);
658                         return $this->$jssetter($value);
659                 }
660                 else if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0)
661                 {
662                         return $this->attachEventHandler($name,$value);
663                 }
664                 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
665                 {
666                         $sets=0;
667                         foreach($this->_m->toArray() as $behavior)
668                         {
669                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&
670                                         (property_exists($behavior,$name)||$behavior->canSetProperty($name)||$behavior->hasEvent($name))) {
671                                         $behavior->$name=$value;
672                                         $sets++;
673                                 }
674                         }
675                         if($sets)return $value;
676
677                 }
678
679                 if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
680                 {
681                         throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
682                 }
683                 else
684                 {
685                         throw new TInvalidOperationException('component_property_undefined',get_class($this),$name);
686                 }
687         }
688
689         /**
690          * Checks if a property value is null, there are no events in the object
691          * event list or global event list registered under the name, and, if
692          * behaviors are enabled,
693          * Do not call this method. This is a PHP magic method that we override
694          * to allow using isset() to detect if a component property is set or not.
695          * This also works for global events.  When behaviors are enabled, it
696          * will check for a behavior of the specified name, and also check
697          * the behavior for events and properties.
698          * @param string the property name or the event name
699          * @since 3.2.3
700          */
701         public function __isset($name)
702         {
703                 if(method_exists($this,$getter='get'.$name))
704                         return $this->$getter()!==null;
705                 else if(method_exists($this,$jsgetter='getjs'.$name))
706                         return $this->$jsgetter()!==null;
707                 else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
708                 {
709                         $name=strtolower($name);
710                         return isset($this->_e[$name])&&$this->_e[$name]->getCount();
711                 }
712                 else if(strncasecmp($name,'fx',2)===0)
713                 {
714                         $name=strtolower($name);
715                         return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount();
716                 }
717                 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
718                 {
719                         if(isset($this->_m[$name]))
720                                 return true;
721                         foreach($this->_m->toArray() as $behavior)
722                         {
723                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled()))
724                                         return isset($behavior->$name);
725                         }
726
727                 }
728                 else
729                         return false;
730         }
731
732         /**
733          * Sets a component property to be null.  Clears the object or global
734          * events. When enabled, loops through all behaviors and unsets the
735          * property or event.
736          * Do not call this method. This is a PHP magic method that we override
737          * to allow using unset() to set a component property to be null.
738          * @param string the property name or the event name
739          * @throws TInvalidOperationException if the property is read only.
740          * @since 3.2.3
741          */
742         public function __unset($name)
743         {
744                 if(method_exists($this,$setter='set'.$name))
745                         $this->$setter(null);
746                 else if(method_exists($this,$jssetter='setjs'.$name))
747                         $this->$jssetter(null);
748                 else if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
749                         $this->_e[strtolower($name)]->clear();
750                 else if(strncasecmp($name,'fx',2)===0)
751                         $this->getEventHandlers($name)->remove(array($this, $name));
752                 else if($this->_m!==null&&$this->_m->getCount()>0&&$this->_behaviorsenabled)
753                 {
754                         if(isset($this->_m[$name]))
755                                 $this->detachBehavior($name);
756                         else {
757                                 $unset=0;
758                                 foreach($this->_m->toArray() as $behavior)
759                                 {
760                                         if((!($behavior instanceof IBehavior)||$behavior->getEnabled())) {
761                                                 unset($behavior->$name);
762                                                 $unset++;
763                                         }
764                                 }
765                                 if(!$unset&&method_exists($this,'get'.$name))
766                                         throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
767                         }
768                 } else if(method_exists($this,'get'.$name))
769                         throw new TInvalidOperationException('component_property_readonly',get_class($this),$name);
770         }
771
772         /**
773          * Determines whether a property is defined.
774          * A property is defined if there is a getter or setter method
775          * defined in the class. Note, property names are case-insensitive.
776          * @param string the property name
777          * @return boolean whether the property is defined
778          */
779         public function hasProperty($name)
780         {
781                 return $this->canGetProperty($name)||$this->canSetProperty($name);
782         }
783
784         /**
785          * Determines whether a property can be read.
786          * A property can be read if the class has a getter method
787          * for the property name. Note, property name is case-insensitive.
788          * This also checks for getjs.  When enabled, it loops through all
789          * active behaviors for the get property when undefined by the object.
790          * @param string the property name
791          * @return boolean whether the property can be read
792          */
793         public function canGetProperty($name)
794         {
795                 if(method_exists($this,'get'.$name)||method_exists($this,'getjs'.$name))
796                         return true;
797                 else if($this->_m!==null&&$this->_behaviorsenabled)
798                 {
799                         foreach($this->_m->toArray() as $behavior)
800                         {
801                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canGetProperty($name))
802                                         return true;
803                         }
804                 }
805                 return false;
806         }
807
808         /**
809          * Determines whether a property can be set.
810          * A property can be written if the class has a setter method
811          * for the property name. Note, property name is case-insensitive.
812          * This also checks for setjs.  When enabled, it loops through all
813          * active behaviors for the set property when undefined by the object.
814          * @param string the property name
815          * @return boolean whether the property can be written
816          */
817         public function canSetProperty($name)
818         {
819                 if(method_exists($this,'set'.$name)||method_exists($this,'setjs'.$name))
820                         return true;
821                 else if($this->_m!==null&&$this->_behaviorsenabled)
822                 {
823                         foreach($this->_m->toArray() as $behavior)
824                         {
825                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->canSetProperty($name))
826                                         return true;
827                         }
828                 }
829                 return false;
830         }
831
832         /**
833          * Evaluates a property path.
834          * A property path is a sequence of property names concatenated by '.' character.
835          * For example, 'Parent.Page' refers to the 'Page' property of the component's
836          * 'Parent' property value (which should be a component also).
837          * When a property is not defined by an object, this also loops through all
838          * active behaviors of the object.
839          * @param string property path
840          * @return mixed the property path value
841          */
842         public function getSubProperty($path)
843         {
844                 $object=$this;
845                 foreach(explode('.',$path) as $property)
846                         $object=$object->$property;
847                 return $object;
848         }
849
850         /**
851          * Sets a value to a property path.
852          * A property path is a sequence of property names concatenated by '.' character.
853          * For example, 'Parent.Page' refers to the 'Page' property of the component's
854          * 'Parent' property value (which should be a component also).
855          * When a property is not defined by an object, this also loops through all
856          * active behaviors of the object.
857          * @param string property path
858          * @param mixed the property path value
859          */
860         public function setSubProperty($path,$value)
861         {
862                 $object=$this;
863                 if(($pos=strrpos($path,'.'))===false)
864                         $property=$path;
865                 else
866                 {
867                         $object=$this->getSubProperty(substr($path,0,$pos));
868                         $property=substr($path,$pos+1);
869                 }
870                 $object->$property=$value;
871         }
872
873         /**
874          * Determines whether an event is defined.
875          * An event is defined if the class has a method whose name is the event name
876          * prefixed with 'on', 'fx', or 'dy'.
877          * Every object responds to every 'fx' and 'dy' event as they are in a universally
878          * accepted event space.  'on' event must be declared by the object.
879          * When enabled, this will loop through all active behaviors for 'on' events
880          * defined by the behavior.
881          * Note, event name is case-insensitive.
882          * @param string the event name
883          * @return boolean
884          */
885         public function hasEvent($name)
886         {
887                 if((strncasecmp($name,'on',2)===0&&method_exists($this,$name))||strncasecmp($name,'fx',2)===0||strncasecmp($name,'dy',2)===0)
888                         return true;
889
890                 else if($this->_m!==null&&$this->_behaviorsenabled)
891                 {
892                         foreach($this->_m->toArray() as $behavior)
893                         {
894                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
895                                         return true;
896                         }
897                 }
898                 return false;
899         }
900
901         /**
902          * Checks if an event has any handlers.  This function also checks through all
903          * the behaviors for 'on' events when behaviors are enabled.
904          * 'dy' dynamic events are not handled by this function.
905          * @param string the event name
906          * @return boolean whether an event has been attached one or several handlers
907          */
908         public function hasEventHandler($name)
909         {
910                 $name=strtolower($name);
911                 if(strncasecmp($name,'fx',2)===0)
912                         return isset(self::$_ue[$name])&&self::$_ue[$name]->getCount()>0;
913                 else
914                 {
915                         if(isset($this->_e[$name])&&$this->_e[$name]->getCount()>0)
916                                 return true;
917                         else if($this->_m!==null&&$this->_behaviorsenabled) {
918                                 foreach($this->_m->toArray() as $behavior)
919                                 {
920                                         if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEventHandler($name))
921                                                 return true;
922                                 }
923                         }
924                 }
925                 return false;
926         }
927
928         /**
929          * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
930          * checks through all the behaviors for 'on' event lists when behaviors are enabled.
931          * @return TPriorityList list of attached event handlers for an event
932          * @throws TInvalidOperationException if the event is not defined
933          */
934         public function getEventHandlers($name)
935         {
936                 if(strncasecmp($name,'on',2)===0&&method_exists($this,$name))
937                 {
938                         $name=strtolower($name);
939                         if(!isset($this->_e[$name]))
940                                 $this->_e[$name]=new TPriorityList;
941                         return $this->_e[$name];
942                 }
943                 else if(strncasecmp($name,'fx',2)===0)
944                 {
945                         $name=strtolower($name);
946                         if(!isset(self::$_ue[$name]))
947                                 self::$_ue[$name]=new TPriorityList;
948                         return self::$_ue[$name];
949                 }
950                 else if($this->_m!==null&&$this->_behaviorsenabled)
951                 {
952                         foreach($this->_m->toArray() as $behavior)
953                         {
954                                 if((!($behavior instanceof IBehavior)||$behavior->getEnabled())&&$behavior->hasEvent($name))
955                                         return $behavior->getEventHandlers($name);
956                         }
957                 }
958                 throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
959         }
960
961         /**
962          * Attaches an event handler to an event.
963          *
964          * The handler must be a valid PHP callback, i.e., a string referring to
965          * a global function name, or an array containing two elements with
966          * the first element being an object and the second element a method name
967          * of the object. In Prado, you can also use method path to refer to
968          * an event handler. For example, array($object,'Parent.buttonClicked')
969          * uses a method path that refers to the method $object->Parent->buttonClicked(...).
970          *
971          * The event handler must be of the following signature,
972          * <code>
973          * function handlerName($sender, $param) {}
974          * function handlerName($sender, $param, $name) {}
975          * </code>
976          * where $sender represents the object that raises the event,
977          * and $param is the event parameter. $name refers to the event name
978          * being handled.
979          *
980          * This is a convenient method to add an event handler.
981          * It is equivalent to {@link getEventHandlers}($name)->add($handler).
982          * For complete management of event handlers, use {@link getEventHandlers}
983          * to get the event handler list first, and then do various
984          * {@link TPriorityList} operations to append, insert or remove
985          * event handlers. You may also do these operations like
986          * getting and setting properties, e.g.,
987          * <code>
988          * $component->OnClick[]=array($object,'buttonClicked');
989          * $component->OnClick->insertAt(0,array($object,'buttonClicked'));
990          * </code>
991          * which are equivalent to the following
992          * <code>
993          * $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
994          * $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
995          * </code>
996          *
997          * Due to the nature of {@link getEventHandlers}, any active behaviors defining
998          * new 'on' events, this method will pass through to the behavior transparently.
999          *
1000          * @param string the event name
1001          * @param callback the event handler
1002          * @param numeric|null the priority of the handler, defaults to null which translates into the
1003          * default priority of 10.0 within {@link TPriorityList}
1004          * @throws TInvalidOperationException if the event does not exist
1005          */
1006         public function attachEventHandler($name,$handler,$priority=null)
1007         {
1008                 $this->getEventHandlers($name)->add($handler,$priority);
1009         }
1010
1011         /**
1012          * Detaches an existing event handler.
1013          * This method is the opposite of {@link attachEventHandler}.  It will detach
1014          * any 'on' events definedb by an objects active behaviors as well.
1015          * @param string event name
1016          * @param callback the event handler to be removed
1017          * @param numeric|false|null the priority of the handler, defaults to false which translates
1018          * to an item of any priority within {@link TPriorityList}; null means the default priority
1019          * @return boolean if the removal is successful
1020          */
1021         public function detachEventHandler($name,$handler,$priority=false)
1022         {
1023                 if($this->hasEventHandler($name))
1024                 {
1025                         try
1026                         {
1027                                 $this->getEventHandlers($name)->remove($handler,$priority);
1028                                 return true;
1029                         }
1030                         catch(Exception $e)
1031                         {
1032                         }
1033                 }
1034                 return false;
1035         }
1036
1037         /**
1038          * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1039          * This method represents the happening of an event and will
1040          * invoke all attached event handlers for the event in {@link TPriorityList} order.
1041          * This method does not handle intra-object/behavior dynamic 'dy' events.
1042          *
1043          * There are ways to handle event responses.  By defailt {@link EVENT_RESULT_FILTER},
1044          * all event responses are stored in an array, filtered for null responses, and returned.
1045          * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1046          * with the sender and param in an array
1047          * <code>
1048          *              $result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1049          * </code>
1050          *
1051          * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1052          * fed forward as the parameters for the next event.  This allows for events to filter data
1053          * directly by affecting the event parameters
1054          *
1055          * If a callable function is set in the response type or the post function filter is specified then the
1056          * result of each called event handler is post processed by the callable function.  Used in
1057          * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1058          *
1059          * When raising a global 'fx' event, registered handlers in the global event list for
1060          * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1061          * these global events are always raised for every global 'fx' event.  The registered handlers for global
1062          * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1063          * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1064          * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1065          * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1066          *
1067          * Behaviors may implement the following functions:
1068          * <code>
1069          *      public function dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1070          *      return $name; //eg, the event name may be filtered/changed
1071          *  }
1072          *      public function dyIntraRaiseEventTestHandler($handler,$sender,$param,$name[, $chain]) {
1073          *      return true; //should this particular handler be executed?  true/false
1074          *  }
1075          *  public function dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response[, $chain]) {
1076          *              //contains the per handler response
1077          *  }
1078          *  public function dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction[, $chain]) {
1079          *              return $responses;
1080          *  }
1081          * </code>
1082          * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1083          * the handler loop.
1084          *
1085          * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1086          * object/behavior event returns the name of the desired event to be raised.  It will pass through
1087          * if no dynamic event is specified, or if the original event name is returned.
1088          * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1089          * called for a specific raised event (and associated event arguments)
1090          * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1091          * of an event handler in the per handler loop.
1092          * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1093          * results from the sum of all event handlers
1094          *
1095          * When handling a catch-all {@link __dycall}, the method name is the name of the event
1096          * and the parameters are the sender, the param, and then the name of the event.
1097          *
1098          * @param string the event name
1099          * @param mixed the event sender object
1100          * @param TEventParameter the event parameter
1101          * @param numeric how the results of the event are tabulated.  default: {@link EVENT_RESULT_FILTER}  The default filters out
1102          *              null responses. optional
1103          * @param function any per handler filtering of the response result needed is passed through
1104          *              this if not null. default: null.  optional
1105          * @return mixed the results of the event
1106          * @throws TInvalidOperationException if the event is undefined
1107          * @throws TInvalidDataValueException If an event handler is invalid
1108          */
1109         public function raiseEvent($name,$sender,$param,$responsetype=null,$postfunction=null)
1110         {
1111                 $p=$param;
1112                 if(is_callable($responsetype))
1113                 {
1114                         $postfunction=$responsetype;
1115                         $responsetype=null;
1116                 }
1117
1118                 if($responsetype===null)
1119                         $responsetype=TEventResults::EVENT_RESULT_FILTER;
1120
1121                 $name=strtolower($name);
1122                 $responses=array();
1123
1124                 $name=$this->dyPreRaiseEvent($name,$sender,$param,$responsetype,$postfunction);
1125
1126                 if($this->hasEventHandler($name)||$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
1127                 {
1128                         $handlers=$this->getEventHandlers($name);
1129                         $handlerArray=$handlers->toArray();
1130                         if(strncasecmp($name,'fx',2)===0&&$this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER))
1131                         {
1132                                 $globalhandlers=$this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1133                                 $handlerArray=array_merge($globalhandlers->toArrayBelowPriority(0),$handlerArray,$globalhandlers->toArrayAbovePriority(0));
1134                         }
1135                         $response=null;
1136                         foreach($handlerArray as $handler)
1137                         {
1138                                 if($this->dyIntraRaiseEventTestHandler($handler,$sender,$param,$name)===false)
1139                                         continue;
1140
1141                                 if(is_string($handler))
1142                                 {
1143                                         if(($pos=strrpos($handler,'.'))!==false)
1144                                         {
1145                                                 $object=$this->getSubProperty(substr($handler,0,$pos));
1146                                                 $method=substr($handler,$pos+1);
1147                                                 if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
1148                                                 {
1149                                                         if($method=='__dycall')
1150                                                                 $response=$object->__dycall($name,array($sender,$param,$name));
1151                                                         else
1152                                                                 $response=$object->$method($sender,$param,$name);
1153                                                 }
1154                                                 else
1155                                                         throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler);
1156                                         }
1157                                         else
1158                                                 $response=call_user_func($handler,$sender,$param,$name);
1159                                 }
1160                                 else if(is_callable($handler,true))
1161                                 {
1162                                         list($object,$method)=$handler;
1163                                         if(is_string($object))
1164                                                 $response=call_user_func($handler,$sender,$param,$name);
1165                                         else
1166                                         {
1167                                                 if(($pos=strrpos($method,'.'))!==false)
1168                                                 {
1169                                                         $object=$this->getSubProperty(substr($method,0,$pos));
1170                                                         $method=substr($method,$pos+1);
1171                                                 }
1172                                                 if(method_exists($object,$method)||strncasecmp($method,'dy',2)===0||strncasecmp($method,'fx',2)===0)
1173                                                 {
1174                                                         if($method=='__dycall')
1175                                                                 $response=$object->__dycall($name,array($sender,$param,$name));
1176                                                         else
1177                                                                 $response=$object->$method($sender,$param,$name);
1178                                                 }
1179                                                 else
1180                                                         throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,$handler[1]);
1181                                         }
1182                                 }
1183                                 else
1184                                         throw new TInvalidDataValueException('component_eventhandler_invalid',get_class($this),$name,gettype($handler));
1185
1186                                 $this->dyIntraRaiseEventPostHandler($name,$sender,$param,$handler,$response);
1187
1188                                 if($postfunction)
1189                                         $response=call_user_func_array($postfunction,array($sender,$param,$this,$response));
1190
1191                                 if($responsetype&TEventResults::EVENT_RESULT_ALL)
1192                                         $responses[]=array('sender'=>$sender,'param'=>$param,'response'=>$response);
1193                                 else
1194                                         $responses[]=$response;
1195
1196                                 if($response!==null&&($responsetype&TEventResults::EVENT_RESULT_FEED_FORWARD))
1197                                         $param=$response;
1198
1199                         }
1200                 }
1201                 else if(strncasecmp($name,'on',2)===0&&!$this->hasEvent($name))
1202                         throw new TInvalidOperationException('component_event_undefined',get_class($this),$name);
1203
1204                 if($responsetype&TEventResults::EVENT_RESULT_FILTER)
1205                         $responses=array_filter($responses);
1206
1207                 $responses=$this->dyPostRaiseEvent($responses,$name,$sender,$param,$responsetype,$postfunction);
1208
1209                 return $responses;
1210         }
1211
1212         /**
1213          * Evaluates a PHP expression in the context of this control.
1214          *
1215          * Behaviors may implement the function:
1216          * <code>
1217          *      public function dyEvaluateExpressionFilter($expression, $chain) {
1218          *              return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1219          * }
1220          * </code>
1221          * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1222          * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1223          * pass-through effect.
1224          *
1225          * @param string PHP expression
1226          * @return mixed the expression result
1227          * @throws TInvalidOperationException if the expression is invalid
1228          */
1229         public function evaluateExpression($expression)
1230         {
1231                 $expression=$this->dyEvaluateExpressionFilter($expression);
1232                 try
1233                 {
1234                         if(eval("\$result=$expression;")===false)
1235                                 throw new Exception('');
1236                         return $result;
1237                 }
1238                 catch(Exception $e)
1239                 {
1240                         throw new TInvalidOperationException('component_expression_invalid',get_class($this),$expression,$e->getMessage());
1241                 }
1242         }
1243
1244         /**
1245          * Evaluates a list of PHP statements.
1246          *
1247          * Behaviors may implement the function:
1248          * <code>
1249          *      public function dyEvaluateStatementsFilter($statements, $chain) {
1250          *              return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1251          * }
1252          * </code>
1253          * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1254          * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1255          * pass-through effect.
1256          *
1257          * @param string PHP statements
1258          * @return string content echoed or printed by the PHP statements
1259          * @throws TInvalidOperationException if the statements are invalid
1260          */
1261         public function evaluateStatements($statements)
1262         {
1263                 $statements=$this->dyEvaluateStatementsFilter($statements);
1264                 try
1265                 {
1266                         ob_start();
1267                         if(eval($statements)===false)
1268                                 throw new Exception('');
1269                         $content=ob_get_contents();
1270                         ob_end_clean();
1271                         return $content;
1272                 }
1273                 catch(Exception $e)
1274                 {
1275                         throw new TInvalidOperationException('component_statements_invalid',get_class($this),$statements,$e->getMessage());
1276                 }
1277         }
1278
1279         /**
1280          * This method is invoked after the component is instantiated by a template.
1281          * When this method is invoked, the component's properties have been initialized.
1282          * The default implementation of this method will invoke
1283          * the potential parent component's {@link addParsedObject}.
1284          * This method can be overridden.
1285          *
1286          * Behaviors may implement the function:
1287          * <code>
1288          *      public function dyCreatedOnTemplate($parent, $chain) {
1289          *              return $chain->dyCreatedOnTemplate($parent); //example
1290          *  }
1291          * </code>
1292          * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1293          * dyCreatedOnTemplate.
1294          *
1295          * @param TComponent potential parent of this control
1296          * @see addParsedObject
1297          */
1298         public function createdOnTemplate($parent)
1299         {
1300                 $parent=$this->dyCreatedOnTemplate($parent);
1301                 $parent->addParsedObject($this);
1302         }
1303
1304         /**
1305          * Processes an object that is created during parsing template.
1306          * The object can be either a component or a static text string.
1307          * This method can be overridden to customize the handling of newly created objects in template.
1308          * Only framework developers and control developers should use this method.
1309          *
1310          * Behaviors may implement the function:
1311          * <code>
1312          *      public function dyAddParsedObject($object[, $chain]) {
1313          *  }
1314          * </code>
1315          * to be executed when addParsedObject is called.  All attached behaviors are notified through
1316          * dyAddParsedObject.
1317          *
1318          * @param string|TComponent text string or component parsed and instantiated in template
1319          * @see createdOnTemplate
1320          */
1321         public function addParsedObject($object)
1322         {
1323                 $this->dyAddParsedObject($object);
1324         }
1325
1326
1327         /**
1328          *      This is the method registered for all instanced objects should a class behavior be added after
1329          * the class is instanced.  Only when the class to which the behavior is being added is in this
1330          * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1331          * @param $sender the application
1332          * @param $param TClassBehaviorEventParameter
1333          * @since 3.2.3
1334          */
1335         public function fxAttachClassBehavior($sender,$param) {
1336                 if(in_array($param->getClass(),$this->getClassHierarchy(true)))
1337                         return $this->attachBehavior($param->getName(),$param->getBehavior(),$param->getPriority());
1338         }
1339
1340
1341         /**
1342          *      This is the method registered for all instanced objects should a class behavior be removed after
1343          * the class is instanced.  Only when the class to which the behavior is being added is in this
1344          * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1345          * @param $sender the application
1346          * @param $param TClassBehaviorEventParameter
1347          * @since 3.2.3
1348          */
1349         public function fxDetachClassBehavior($sender,$param) {
1350                 if(in_array($param->getClass(),$this->getClassHierarchy(true)))
1351                         return $this->detachBehavior($param->getName(),$param->getPriority());
1352         }
1353
1354
1355         /**
1356          *      This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1357          * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1358          * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1359          * This is done so class behaviors are added last first.
1360          * @param string name the key of the class behavior
1361          * @param object|string class behavior or name of the object behavior per instance
1362          * @param string|class string of class or class on which to attach this behavior.  Defaults to null which will error
1363          *      but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1364          * it should extend.
1365          * <code>
1366          * TPanel::attachClassBehavior('javascripts', (new TJsPanelBehavior())->init($this));
1367          * </code>
1368          * @param numeric|null priority of behavior, default: null the default priority of the {@link TPriorityList}  Optional.
1369          * @throws TInvalidOperationException if the class behavior is being added to a {@link TComponent}; due to recursion.
1370          * @throws TInvalidOperationException if the class behavior is already defined
1371          * @since 3.2.3
1372          */
1373         public static function attachClassBehavior($name,$behavior,$class=null,$priority=null) {
1374                 if(!$class&&function_exists('get_called_class'))
1375                         $class=get_called_class();
1376                 if(!$class)
1377                         throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1378
1379                 if(!is_string($name))
1380                         $name=get_class($name);
1381                 $class=strtolower($class);
1382                 if($class==='tcomponent')
1383                         throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1384                 if(empty(self::$_um[$class]))
1385                         self::$_um[$class]=array();
1386                 if(isset(self::$_um[$class][$name]))
1387                         throw new TInvalidOperationException('component_class_behavior_defined',$class,$name);
1388                 $param=new TClassBehaviorEventParameter($class,$name,$behavior,$priority);
1389                 self::$_um[$class]=array($name=>$param)+self::$_um[$class];
1390                 $behaviorObject=is_string($behavior)?new $behavior:$behavior;
1391                 return $behaviorObject->raiseEvent('fxAttachClassBehavior',null,$param);
1392         }
1393
1394
1395         /**
1396          *      This will remove a behavior from a class.  It unregisters it from future instances and
1397          * pulls the changes from all the instances that are listening as well.
1398          * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1399          * @param $name the key of the class behavior
1400          * @param $class string class on which to attach this behavior.  Defaults to null.
1401          * @param $priority numeric|null|false priority.  false is any priority, null is default
1402          *              {@link TPriorityList} priority, and numeric is a specific priority.
1403          * @throws Exception if the the class cannot be derived from Late Static Binding and is not
1404          * not supplied as a parameter.
1405          * @since 3.2.3
1406          */
1407         public static function detachClassBehavior($name,$class=null,$priority=false) {
1408                 if(!$class&&function_exists('get_called_class'))
1409                         $class=get_called_class();
1410                 if(!$class)
1411                         throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1412
1413                 $class=strtolower($class);
1414                 if(!is_string($name))
1415                         $name=get_class($name);
1416                 if(empty(self::$_um[$class])||!isset(self::$_um[$class][$name]))
1417                         return false;
1418                 $param=self::$_um[$class][$name];
1419                 $behavior=$param->getBehavior();
1420                 unset(self::$_um[$class][$name]);
1421                 $behaviorObject=is_string($behavior)?new $behavior:$behavior;
1422                 return $behaviorObject->raiseEvent('fxDetachClassBehavior',null,$param);
1423         }
1424
1425         /**
1426          * Returns the named behavior object.
1427          * The name 'asa' stands for 'as a'.
1428          * @param string the behavior name
1429          * @return IBehavior the behavior object, or null if the behavior does not exist
1430          * @since 3.2.3
1431          */
1432         public function asa($behaviorname)
1433         {
1434                 return isset($this->_m[$behaviorname])?$this->_m[$behaviorname]:null;
1435         }
1436
1437         /**
1438          * Returns whether or not the object or any of the behaviors are of a particular class.
1439          * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1440          * It then checks each Behavior.  If a behavior implements {@link IInstanceCheck},
1441          * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1442          * then this method returns true.  If the behavior instance checking function returns false,
1443          * then no further checking is performed as it is assumed to be correct.
1444          *
1445          * If the behavior instance check function returns nothing or null or the behavior
1446          * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1447          * The default isa behavior is to check if the behavior is an instanceof the class.
1448          *
1449          * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1450          * act as a completely different object.
1451          *
1452          * @param class or string
1453          * @return boolean whether or not the object or a behavior is an instance of a particular class
1454          * @since 3.2.3
1455          */
1456         public function isa($class)
1457         {
1458                 if($this instanceof $class)
1459                         return true;
1460                 if($this->_m!==null&&$this->_behaviorsenabled)
1461                         foreach($this->_m->toArray() as $behavior){
1462                                 if(($behavior instanceof IBehavior)&&!$behavior->getEnabled())
1463                                         continue;
1464
1465                                 $check = null;
1466                                 if(($behavior->isa('IInstanceCheck'))&&$check=$behavior->isinstanceof($class,$this))
1467                                         return true;
1468                                 if($check===null&&($behavior->isa($class)))
1469                                         return true;
1470                         }
1471                 return false;
1472         }
1473
1474         /**
1475          * Attaches a list of behaviors to the component.
1476          * Each behavior is indexed by its name and should be an instance of
1477          * {@link IBehavior}, a string specifying the behavior class, or a
1478          * {@link TClassBehaviorEventParameter}.
1479          * @param array list of behaviors to be attached to the component
1480          * @since 3.2.3
1481          */
1482         public function attachBehaviors($behaviors)
1483         {
1484                 foreach($behaviors as $name=>$behavior)
1485                         if($behavior instanceof TClassBehaviorEventParameter)
1486                                 $this->attachBehavior($behavior->getName(),$behavior->getBehavior(),$behavior->getPriority());
1487                         else
1488                                 $this->attachBehavior($name,$behavior);
1489         }
1490
1491         /**
1492          * Detaches select behaviors from the component.
1493          * Each behavior is indexed by its name and should be an instance of
1494          * {@link IBehavior}, a string specifying the behavior class, or a
1495          * {@link TClassBehaviorEventParameter}.
1496          * @param array list of behaviors to be detached from the component
1497          * @since 3.2.3
1498          */
1499         public function detachBehaviors($behaviors)
1500         {
1501                 if($this->_m!==null)
1502                 {
1503                         foreach($behaviors as $name=>$behavior)
1504                                 if($behavior instanceof TClassBehaviorEventParameter)
1505                                         $this->detachBehavior($behavior->getName(),$behavior->getPriority());
1506                                 else
1507                                         $this->detachBehavior(is_string($behavior)?$behavior:$name);
1508                 }
1509         }
1510
1511         /**
1512          * Detaches all behaviors from the component.
1513          * @since 3.2.3
1514          */
1515         public function clearBehaviors()
1516         {
1517                 if($this->_m!==null)
1518                 {
1519                         foreach($this->_m->toArray() as $name=>$behavior)
1520                                 $this->detachBehavior($name);
1521                         $this->_m=null;
1522                 }
1523         }
1524
1525         /**
1526          * Attaches a behavior to this component.
1527          * This method will create the behavior object based on the given
1528          * configuration. After that, the behavior object will be initialized
1529          * by calling its {@link IBehavior::attach} method.
1530          *
1531          * Already attached behaviors may implement the function:
1532          * <code>
1533          *      public function dyAttachBehavior($name,$behavior[, $chain]) {
1534          *  }
1535          * </code>
1536          * to be executed when attachBehavior is called.  All attached behaviors are notified through
1537          * dyAttachBehavior.
1538          *
1539          * @param string the behavior's name. It should uniquely identify this behavior.
1540          * @param mixed the behavior configuration. This is passed as the first
1541          * parameter to {@link YiiBase::createComponent} to create the behavior object.
1542          * @return IBehavior the behavior object
1543          * @since 3.2.3
1544          */
1545         public function attachBehavior($name,$behavior,$priority=null)
1546         {
1547                 if(is_string($behavior))
1548                         $behavior=Prado::createComponent($behavior);
1549                 if(!($behavior instanceof IBaseBehavior))
1550                         throw new TInvalidDataTypeException('component_not_a_behavior',get_class($behavior));
1551                 if($behavior instanceof IBehavior)
1552                         $behavior->setEnabled(true);
1553                 if($this->_m===null)
1554                         $this->_m=new TPriorityMap;
1555                 $behavior->attach($this);
1556                 $this->dyAttachBehavior($name,$behavior);
1557                 $this->_m->add($name,$behavior,$priority);
1558                 return $behavior;
1559         }
1560
1561         /**
1562          * Detaches a behavior from the component.
1563          * The behavior's {@link IBehavior::detach} method will be invoked.
1564          *
1565          * Behaviors may implement the function:
1566          * <code>
1567          *      public function dyDetachBehavior($name,$behavior[, $chain]) {
1568          *  }
1569          * </code>
1570          * to be executed when detachBehavior is called.  All attached behaviors are notified through
1571          * dyDetachBehavior.
1572          *
1573          * @param string the behavior's name. It uniquely identifies the behavior.
1574          * @param numeric the behavior's priority. This defaults to false, aka any priority.
1575          * @return IBehavior the detached behavior. Null if the behavior does not exist.
1576          * @since 3.2.3
1577          */
1578         public function detachBehavior($name,$priority=false)
1579         {
1580                 if($this->_m!=null&&isset($this->_m[$name]))
1581                 {
1582                         $this->_m[$name]->detach($this);
1583                         $behavior=$this->_m->itemAt($name);
1584                         $this->_m->remove($name,$priority);
1585                         $this->dyDetachBehavior($name,$behavior);
1586                         return $behavior;
1587                 }
1588         }
1589
1590         /**
1591          * Enables all behaviors attached to this component independent of the behaviors
1592          *
1593          * Behaviors may implement the function:
1594          * <code>
1595          *      public function dyEnableBehaviors($name,$behavior[, $chain]) {
1596          *  }
1597          * </code>
1598          * to be executed when enableBehaviors is called.  All attached behaviors are notified through
1599          * dyEnableBehaviors.
1600          * @since 3.2.3
1601          */
1602         public function enableBehaviors()
1603         {
1604                 if(!$this->_behaviorsenabled)
1605                 {
1606                         $this->_behaviorsenabled=true;
1607                         $this->dyEnableBehaviors();
1608                 }
1609         }
1610
1611         /**
1612          * Disables all behaviors attached to this component independent of the behaviors
1613          *
1614          * Behaviors may implement the function:
1615          * <code>
1616          *      public function dyDisableBehaviors($name,$behavior[, $chain]) {
1617          *  }
1618          * </code>
1619          * to be executed when disableBehaviors is called.  All attached behaviors are notified through
1620          * dyDisableBehaviors.
1621          * @since 3.2.3
1622          */
1623         public function disableBehaviors()
1624         {
1625                 if($this->_behaviorsenabled)
1626                 {
1627                         $this->dyDisableBehaviors();
1628                         $this->_behaviorsenabled=false;
1629                 }
1630         }
1631
1632
1633         /**
1634          * Returns if all the behaviors are turned on or off for the object.
1635          * @return boolean whether or not all behaviors are enabled (true) or not (false)
1636          * @since 3.2.3
1637          */
1638         public function getBehaviorsEnabled()
1639         {
1640                 return $this->_behaviorsenabled;
1641         }
1642
1643         /**
1644          * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
1645          * A behavior is only effective when it is enabled.
1646          * A behavior is enabled when first attached.
1647          *
1648          * Behaviors may implement the function:
1649          * <code>
1650          *      public function dyEnableBehavior($name,$behavior[, $chain]) {
1651          *  }
1652          * </code>
1653          * to be executed when enableBehavior is called.  All attached behaviors are notified through
1654          * dyEnableBehavior.
1655          *
1656          * @param string the behavior's name. It uniquely identifies the behavior.
1657          * @since 3.2.3
1658          */
1659         public function enableBehavior($name)
1660         {
1661                 if($this->_m!=null&&isset($this->_m[$name])){
1662                         if($this->_m[$name] instanceof IBehavior) {
1663                                 $this->_m[$name]->setEnabled(true);
1664                                 $this->dyEnableBehavior($name,$this->_m[$name]);
1665                                 return true;
1666                         }
1667                         return false;
1668                 }
1669                 return null;
1670         }
1671
1672         /**
1673          * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
1674          * A behavior is only effective when it is enabled.
1675          *
1676          * Behaviors may implement the function:
1677          * <code>
1678          *      public function dyDisableBehavior($name,$behavior[, $chain]) {
1679          *  }
1680          * </code>
1681          * to be executed when disableBehavior is called.  All attached behaviors are notified through
1682          * dyDisableBehavior.
1683          *
1684          * @param string the behavior's name. It uniquely identifies the behavior.
1685          * @since 3.2.3
1686          */
1687         public function disableBehavior($name)
1688         {
1689                 if($this->_m!=null&&isset($this->_m[$name])){
1690                         if($this->_m[$name] instanceof IBehavior) {
1691                                 $this->_m[$name]->setEnabled(false);
1692                                 $this->dyDisableBehavior($name,$this->_m[$name]);
1693                                 return true;
1694                         }
1695                         return false;
1696                 }
1697                 return null;
1698         }
1699
1700         /**
1701          * Do not call this method. This is a PHP magic method that will be called automatically
1702          * after any unserialization; it can perform reinitialization tasks on the object.
1703          */
1704         public function __wakeup()
1705         {
1706                 if ($this->_e===null)
1707                         $this->_e = array();
1708         }
1709
1710         /**
1711          * Returns an array with the names of all variables of that object that should be serialized.
1712          * Do not call this method. This is a PHP magic method that will be called automatically
1713          * prior to any serialization.
1714          */
1715         public function __sleep()
1716         {
1717                 $a = (array)$this;
1718                 $a = array_keys($a);
1719                 $exprops = array();
1720                 if($this->_listeningenabled===false)
1721                         $exprops[] = "\0TComponent\0_listeningenabled";
1722                 if($this->_behaviorsenabled===true)
1723                         $exprops[] = "\0TComponent\0_behaviorsenabled";
1724                 if ($this->_e===array())
1725                         $exprops[] = "\0TComponent\0_e";
1726                 if ($this->_m===null)
1727                         $exprops[] = "\0TComponent\0_m";
1728                 return array_diff($a,$exprops);
1729         }
1730 }
1731
1732
1733 /**
1734  * IDynamicMethods interface.
1735  * IDynamicMethods marks an object to receive undefined global or dynamic events.
1736  *
1737  * @author Brad Anderson <javalizard@mac.com>
1738  * @version $Id$
1739  * @package System
1740  * @since 3.2.3
1741  */
1742 interface IDynamicMethods
1743 {
1744         public function __dycall($method,$args);
1745 }
1746
1747
1748
1749 /**
1750  * TClassBehaviorEventParameter class.
1751  * TClassBehaviorEventParameter is the parameter sent with the class behavior changes.
1752  *
1753  * @author Brad Anderson <javalizard@mac.com>
1754  * @version $Id$
1755  * @package System
1756  * @since 3.2.3
1757  */
1758 class TClassBehaviorEventParameter extends TEventParameter
1759 {
1760         private $_class;
1761         private $_name;
1762         private $_behavior;
1763         private $_priority;
1764
1765         /**
1766          * Holds the parameters for the Class Behavior Events
1767          *      @param string $class this is the class to get the behavior
1768          *      @param string $name the name of the behavior
1769          *      @param object $behavior this is the behavior to implement the class behavior
1770          */
1771         public function __construct($class,$name,$behavior,$priority)
1772         {
1773                 $this->_class=$class;
1774                 $this->_name=$name;
1775                 $this->_behavior=$behavior;
1776                 $this->_priority=$priority;
1777         }
1778
1779         /**
1780          * This is the class to get the behavior
1781          * @return string the class to get the behavior
1782          */
1783         public function getClass()
1784         {
1785                 return $this->_class;
1786         }
1787
1788         /**
1789          * name of the behavior
1790          * @return string the name to get the behavior
1791          */
1792         public function getName()
1793         {
1794                 return $this->_name;
1795         }
1796
1797         /**
1798          * This is the behavior which the class is to get
1799          * @return object the behavior to implement
1800          */
1801         public function getBehavior()
1802         {
1803                 return $this->_behavior;
1804         }
1805
1806         /**
1807          * This is the priority which the behavior is to get
1808          * @return numeric the priority of the behavior
1809          */
1810         public function getPriority()
1811         {
1812                 return $this->_priority;
1813         }
1814 }
1815
1816
1817 /**
1818  * TEnumerable class.
1819  * TEnumerable is the base class for all enumerable types.
1820  * To define an enumerable type, extend TEnumberable and define string constants.
1821  * Each constant represents an enumerable value.
1822  * The constant name must be the same as the constant value.
1823  * For example,
1824  * <code>
1825  * class TTextAlign extends TEnumerable
1826  * {
1827  *     const Left='Left';
1828  *     const Right='Right';
1829  * }
1830  * </code>
1831  * Then, one can use the enumerable values such as TTextAlign::Left and
1832  * TTextAlign::Right.
1833  *
1834  * @author Qiang Xue <qiang.xue@gmail.com>
1835  * @package System
1836  * @since 3.0
1837  */
1838 class TEnumerable implements Iterator
1839 {
1840         private $_enums=array();
1841
1842         public function __construct() {
1843                 $reflection=new ReflectionClass($this);
1844                 $this->_enums=$reflection->getConstants();
1845         }
1846
1847         public function current() {
1848                 return current($this->_enums);
1849         }
1850
1851         public function key() {
1852                 return key($this->_enums);
1853         }
1854
1855         public function next() {
1856                 return next($this->_enums);
1857         }
1858
1859         public function rewind() {
1860                 reset($this->_enums);
1861         }
1862
1863         public function valid() {
1864                 return $this->current()!==false;
1865         }
1866 }
1867
1868 /**
1869  * TPropertyValue class
1870  *
1871  * TPropertyValue is a utility class that provides static methods
1872  * to convert component property values to specific types.
1873  *
1874  * TPropertyValue is commonly used in component setter methods to ensure
1875  * the new property value is of specific type.
1876  * For example, a boolean-typed property setter method would be as follows,
1877  * <code>
1878  * function setPropertyName($value) {
1879  *     $value=TPropertyValue::ensureBoolean($value);
1880  *     // $value is now of boolean type
1881  * }
1882  * </code>
1883  *
1884  * Properties can be of the following types with specific type conversion rules:
1885  * - string: a boolean value will be converted to 'true' or 'false'.
1886  * - boolean: string 'true' (case-insensitive) will be converted to true,
1887  *            string 'false' (case-insensitive) will be converted to false.
1888  * - integer
1889  * - float
1890  * - array: string starting with '(' and ending with ')' will be considered as
1891  *          as an array expression and will be evaluated. Otherwise, an array
1892  *          with the value to be ensured is returned.
1893  * - object
1894  * - enum: enumerable type, represented by an array of strings.
1895  *
1896  * @author Qiang Xue <qiang.xue@gmail.com>
1897  * @package System
1898  * @since 3.0
1899  */
1900 class TPropertyValue
1901 {
1902         /**
1903          * Converts a value to boolean type.
1904          * Note, string 'true' (case-insensitive) will be converted to true,
1905          * string 'false' (case-insensitive) will be converted to false.
1906          * If a string represents a non-zero number, it will be treated as true.
1907          * @param mixed the value to be converted.
1908          * @return boolean
1909          */
1910         public static function ensureBoolean($value)
1911         {
1912                 if (is_string($value))
1913                         return strcasecmp($value,'true')==0 || $value!=0;
1914                 else
1915                         return (boolean)$value;
1916         }
1917
1918         /**
1919          * Converts a value to string type.
1920          * Note, a boolean value will be converted to 'true' if it is true
1921          * and 'false' if it is false.
1922          * @param mixed the value to be converted.
1923          * @return string
1924          */
1925         public static function ensureString($value)
1926         {
1927                 if (TJavaScript::isJsLiteral($value))
1928                         return $value;
1929                 if (is_bool($value))
1930                         return $value?'true':'false';
1931                 else
1932                         return (string)$value;
1933         }
1934
1935         /**
1936          * Converts a value to integer type.
1937          * @param mixed the value to be converted.
1938          * @return integer
1939          */
1940         public static function ensureInteger($value)
1941         {
1942                 return (integer)$value;
1943         }
1944
1945         /**
1946          * Converts a value to float type.
1947          * @param mixed the value to be converted.
1948          * @return float
1949          */
1950         public static function ensureFloat($value)
1951         {
1952                 return (float)$value;
1953         }
1954
1955         /**
1956          * Converts a value to array type. If the value is a string and it is
1957          * in the form (a,b,c) then an array consisting of each of the elements
1958          * will be returned. If the value is a string and it is not in this form
1959          * then an array consisting of just the string will be returned. If the value
1960          * is not a string then
1961          * @param mixed the value to be converted.
1962          * @return array
1963          */
1964         public static function ensureArray($value)
1965         {
1966                 if(is_string($value))
1967                 {
1968                         $value = trim($value);
1969                         $len = strlen($value);
1970                         if ($len >= 2 && $value[0] == '(' && $value[$len-1] == ')')
1971                         {
1972                                 eval('$array=array'.$value.';');
1973                                 return $array;
1974                         }
1975                         else
1976                                 return $len>0?array($value):array();
1977                 }
1978                 else
1979                         return (array)$value;
1980         }
1981
1982         /**
1983          * Converts a value to object type.
1984          * @param mixed the value to be converted.
1985          * @return object
1986          */
1987         public static function ensureObject($value)
1988         {
1989                 return (object)$value;
1990         }
1991
1992         /**
1993          * Converts a value to enum type.
1994          *
1995          * This method checks if the value is of the specified enumerable type.
1996          * A value is a valid enumerable value if it is equal to the name of a constant
1997          * in the specified enumerable type (class).
1998          * For more details about enumerable, see {@link TEnumerable}.
1999          *
2000          * For backward compatibility, this method also supports sanity
2001          * check of a string value to see if it is among the given list of strings.
2002          * @param mixed the value to be converted.
2003          * @param mixed class name of the enumerable type, or array of valid enumeration values. If this is not an array,
2004          * the method considers its parameters are of variable length, and the second till the last parameters are enumeration values.
2005          * @return string the valid enumeration value
2006          * @throws TInvalidDataValueException if the original value is not in the string array.
2007          */
2008         public static function ensureEnum($value,$enums)
2009         {
2010                 static $types=array();
2011                 if(func_num_args()===2 && is_string($enums))
2012                 {
2013                         if(!isset($types[$enums]))
2014                                 $types[$enums]=new ReflectionClass($enums);
2015                         if($types[$enums]->hasConstant($value))
2016                                 return $value;
2017                         else
2018                                 throw new TInvalidDataValueException(
2019                                         'propertyvalue_enumvalue_invalid',$value,
2020                                                 implode(' | ',$types[$enums]->getConstants()));
2021                 }
2022                 else if(!is_array($enums))
2023                 {
2024                         $enums=func_get_args();
2025                         array_shift($enums);
2026                 }
2027                 if(in_array($value,$enums,true))
2028                         return $value;
2029                 else
2030                         throw new TInvalidDataValueException('propertyvalue_enumvalue_invalid',$value,implode(' | ',$enums));
2031         }
2032
2033         /**
2034          * Converts the value to 'null' if the given value is empty
2035          * @param mixed value to be converted
2036          * @return mixed input or NULL if input is empty
2037          */
2038         public static function ensureNullIfEmpty($value)
2039         {
2040                 return empty($value)?null:$value;
2041         }
2042 }
2043
2044 /**
2045  * TEventParameter class.
2046  * TEventParameter is the base class for all event parameter classes.
2047  *
2048  * @author Qiang Xue <qiang.xue@gmail.com>
2049  * @package System
2050  * @since 3.0
2051  */
2052 class TEventParameter extends TComponent
2053 {
2054 }
2055
2056 class TEventResults extends TEnumerable {
2057         const EVENT_RESULT_FEED_FORWARD=1;
2058         const EVENT_RESULT_FILTER=2;
2059         const EVENT_RESULT_ALL=4;
2060 }
2061
2062 /**
2063  * TComponentReflection class.
2064  *
2065  * TComponentReflection provides functionalities to inspect the public/protected
2066  * properties, events and methods defined in a class.
2067  *
2068  * The following code displays the properties and events defined in {@link TDataGrid},
2069  * <code>
2070  *   $reflection=new TComponentReflection('TDataGrid');
2071  *   Prado::varDump($reflection->getProperties());
2072  *   Prado::varDump($reflection->getEvents());
2073  * </code>
2074  *
2075  * @author Qiang Xue <qiang.xue@gmail.com>
2076  * @package System
2077  * @since 3.0
2078  */
2079 class TComponentReflection extends TComponent
2080 {
2081         private $_className;
2082         private $_properties=array();
2083         private $_events=array();
2084         private $_methods=array();
2085
2086         /**
2087          * Constructor.
2088          * @param object|string the component instance or the class name
2089          * @throws TInvalidDataTypeException if the object is not a component
2090          */
2091         public function __construct($component)
2092         {
2093                 if(is_string($component) && class_exists($component,false))
2094                         $this->_className=$component;
2095                 else if(is_object($component))
2096                         $this->_className=get_class($component);
2097                 else
2098                         throw new TInvalidDataTypeException('componentreflection_class_invalid');
2099                 $this->reflect();
2100         }
2101
2102         private function isPropertyMethod($method)
2103         {
2104                 $methodName=$method->getName();
2105                 return $method->getNumberOfRequiredParameters()===0
2106                                 && strncasecmp($methodName,'get',3)===0
2107                                 && isset($methodName[3]);
2108         }
2109
2110         private function isEventMethod($method)
2111         {
2112                 $methodName=$method->getName();
2113                 return strncasecmp($methodName,'on',2)===0
2114                                 && isset($methodName[2]);
2115         }
2116
2117         private function reflect()
2118         {
2119                 $class=new ReflectionClass($this->_className);
2120                 $properties=array();
2121                 $events=array();
2122                 $methods=array();
2123                 $isComponent=is_subclass_of($this->_className,'TComponent') || strcasecmp($this->_className,'TComponent')===0;
2124                 foreach($class->getMethods() as $method)
2125                 {
2126                         if($method->isPublic() || $method->isProtected())
2127                         {
2128                                 $methodName=$method->getName();
2129                                 if(!$method->isStatic() && $isComponent)
2130                                 {
2131                                         if($this->isPropertyMethod($method))
2132                                                 $properties[substr($methodName,3)]=$method;
2133                                         else if($this->isEventMethod($method))
2134                                         {
2135                                                 $methodName[0]='O';
2136                                                 $events[$methodName]=$method;
2137                                         }
2138                                 }
2139                                 if(strncmp($methodName,'__',2)!==0)
2140                                         $methods[$methodName]=$method;
2141                         }
2142                 }
2143                 $reserved=array();
2144                 ksort($properties);
2145                 foreach($properties as $name=>$method)
2146                 {
2147                         $this->_properties[$name]=array(
2148                                 'type'=>$this->determinePropertyType($method),
2149                                 'readonly'=>!$class->hasMethod('set'.$name),
2150                                 'protected'=>$method->isProtected(),
2151                                 'class'=>$method->getDeclaringClass()->getName(),
2152                                 'comments'=>$method->getDocComment()
2153                         );
2154                         $reserved['get'.strtolower($name)]=1;
2155                         $reserved['set'.strtolower($name)]=1;
2156                 }
2157                 ksort($events);
2158                 foreach($events as $name=>$method)
2159                 {
2160                         $this->_events[$name]=array(
2161                                 'class'=>$method->getDeclaringClass()->getName(),
2162                                 'protected'=>$method->isProtected(),
2163                                 'comments'=>$method->getDocComment()
2164                         );
2165                         $reserved[strtolower($name)]=1;
2166                 }
2167                 ksort($methods);
2168                 foreach($methods as $name=>$method)
2169                 {
2170                         if(!isset($reserved[strtolower($name)]))
2171                                 $this->_methods[$name]=array(
2172                                         'class'=>$method->getDeclaringClass()->getName(),
2173                                         'protected'=>$method->isProtected(),
2174                                         'static'=>$method->isStatic(),
2175                                         'comments'=>$method->getDocComment()
2176                                 );
2177                 }
2178         }
2179
2180         /**
2181          * Determines the property type.
2182          * This method uses the doc comment to determine the property type.
2183          * @param ReflectionMethod
2184          * @return string the property type, '{unknown}' if type cannot be determined from comment
2185          */
2186         protected function determinePropertyType($method)
2187         {
2188                 $comment=$method->getDocComment();
2189                 if(preg_match('/@return\\s+(.*?)\\s+/',$comment,$matches))
2190                         return $matches[1];
2191                 else
2192                         return '{unknown}';
2193         }
2194
2195         /**
2196          * @return string class name of the component
2197          */
2198         public function getClassName()
2199         {
2200                 return $this->_className;
2201         }
2202
2203         /**
2204          * @return array list of component properties. Array keys are property names.
2205          * Each array element is of the following structure:
2206          * [type]=>property type,
2207          * [readonly]=>whether the property is read-only,
2208          * [protected]=>whether the method is protected or not
2209          * [class]=>the class where the property is inherited from,
2210          * [comments]=>comments associated with the property.
2211          */
2212         public function getProperties()
2213         {
2214                 return $this->_properties;
2215         }
2216
2217         /**
2218          * @return array list of component events. Array keys are event names.
2219          * Each array element is of the following structure:
2220          * [protected]=>whether the event is protected or not
2221          * [class]=>the class where the event is inherited from.
2222          * [comments]=>comments associated with the event.
2223          */
2224         public function getEvents()
2225         {
2226                 return $this->_events;
2227         }
2228
2229         /**
2230          * @return array list of public/protected methods. Array keys are method names.
2231          * Each array element is of the following structure:
2232          * [protected]=>whether the method is protected or not
2233          * [static]=>whether the method is static or not
2234          * [class]=>the class where the property is inherited from,
2235          * [comments]=>comments associated with the event.
2236          */
2237         public function getMethods()
2238         {
2239                 return $this->_methods;
2240         }
2241 }
2242
2243 /**
2244  * IBaseBehavior interface is the base behavior class from which all other
2245  * behaviors types are derived
2246  *
2247  * @author Brad Anderson <javalizard@mac.com>
2248  * @version $Id$
2249  * @package System
2250  * @since 3.2.3
2251  */
2252 interface IBaseBehavior {
2253         /**
2254          * Attaches the behavior object to the component.
2255          * @param CComponent the component that this behavior is to be attached to.
2256          */
2257         public function attach($component);
2258         /**
2259          * Detaches the behavior object from the component.
2260          * @param CComponent the component that this behavior is to be detached from.
2261          */
2262         public function detach($component);
2263 }
2264
2265 /**
2266  * IBehavior interfaces is implemented by instance behavior classes.
2267  *
2268  * A behavior is a way to enhance a component with additional methods and
2269  * events that are defined in the behavior class and not available in the
2270  * class.  Objects may signal behaviors through dynamic events.
2271  *
2272  * @author Brad Anderson <javalizard@mac.com>
2273  * @version $Id$
2274  * @package System
2275  * @since 3.2.3
2276  */
2277 interface IBehavior extends IBaseBehavior
2278 {
2279         /**
2280          * @return boolean whether this behavior is enabled
2281          */
2282         public function getEnabled();
2283         /**
2284          * @param boolean whether this behavior is enabled
2285          */
2286         public function setEnabled($value);
2287 }
2288
2289
2290 /**
2291  * IClassBehavior interface is implements behaviors across all instances of
2292  * a particular class
2293  *
2294  * Any calls to functions not present in the original object but to behaviors
2295  * derived from this class, will have inserted as the first argument parameter
2296  * the object containing the behavior.
2297  *
2298  * For example:
2299  * <code>
2300  * $objWithClassBehavior->MethodOfClassBehavior(1, 20);
2301  * </code>
2302  * will be acted within the class behavior like this:
2303  * <code>
2304  * public function MethodOfClassBehavior($object, $firstParam, $secondParam){
2305  *      // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20
2306  * }
2307  * </code>
2308  *
2309  * This also holds for 'dy' events as well.  For dynamic events, method arguments would be:
2310  * <code>
2311  * public function dyMethodOfClassBehavior($object, $firstParam, $secondParam, $callchain){
2312  *      // $object === $objWithClassBehavior, $firstParam === 1, $secondParam === 20, $callchain instanceof {@link TCallChain}
2313  * }
2314  * </code>
2315  *
2316  * @author Brad Anderson <javalizard@mac.com>
2317  * @version $Id$
2318  * @package System
2319  * @since 3.2.3
2320  */
2321 interface IClassBehavior extends IBaseBehavior {
2322 }
2323
2324
2325 /**
2326  * IInstanceCheck This interface allows objects to determine their own
2327  * 'instanceof' results when {@link TComponent::isa} is called.  This is
2328  * important with behaviors because behaviors may want to look like
2329  * particular objects other than themselves.
2330  *
2331  * @author Brad Anderson <javalizard@mac.com>
2332  * @version $Id$
2333  * @package System
2334  * @since 3.2.3
2335  */
2336 interface IInstanceCheck {
2337         /**
2338          * The method checks $this or, if needed, the parameter $instance is of type
2339          * class.  In the case of a Class Behavior, the instance to which the behavior
2340          * is attached may be important to determine if $this is an instance
2341          * of a particular class.
2342          * @param class|string the component that this behavior is checking if it is an instanceof.
2343          * @param object the object which the behavior is attached to.  default: null
2344          * @return boolean|null if the this or the instance is of type class.  When null, no information could be derived and
2345          * the default mechanisms take over.
2346          */
2347         public function isinstanceof($class,$instance=null);
2348 }
2349
2350 /**
2351  * TJavaScriptLiteral class that encloses string literals that are not
2352  * supposed to be escaped by {@link TJavaScript::encode() }
2353  *
2354  * Since Prado 3.2 all the data that gets sent clientside inside a javascript statement
2355  * is encoded by default to avoid any kind of injection.
2356  * Sometimes there's the need to bypass this encoding and send raw javascript code.
2357  * To ensure that a string doesn't get encoded by {@link TJavaScript::encode() },
2358  * construct a new TJavaScriptLiteral:
2359  * <code>
2360  * // a javascript test string
2361  * $js="alert('hello')";
2362  * // the string in $raw will not be encoded when sent clientside inside a javascript block
2363  * $raw=new TJavaScriptLiteral($js);
2364  * // shortened form
2365  * $raw=_js($js);
2366  * </code>
2367  *
2368  * @package System
2369  * @since 3.2.0
2370  */
2371 class TJavaScriptLiteral
2372 {
2373         protected $_s;
2374
2375         public function __construct($s)
2376         {
2377                 $this->_s = $s;
2378         }
2379
2380         public function __toString()
2381         {
2382                 return (string)$this->_s;
2383         }
2384
2385         public function toJavaScriptLiteral()
2386         {
2387                 return $this->__toString();
2388         }
2389 }
2390
2391 /**
2392  * TJavaScriptString class is an internal class that marks strings that will be
2393  * forcibly encoded when rendered inside a javascript block
2394  *
2395  * @package System
2396  * @since 3.2.0
2397  */
2398 class TJavaScriptString extends TJavaScriptLiteral
2399 {
2400         public function toJavaScriptLiteral()
2401         {
2402                 return TJavaScript::jsonEncode((string)$this->_s,JSON_HEX_QUOT | JSON_HEX_APOS | JSON_HEX_TAG);
2403         }
2404 }
2405