Passed
Push — master ( 0aca7d...60550c )
by Fabio
06:00
created

TComponent::asa()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 8
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 56
rs 8.8333
1
<?php
2
/**
3
 * TComponent, TPropertyValue classes
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 *
7
 * Global Events, intra-object events, Class behaviors, expanded behaviors
8
 * @author Brad Anderson <[email protected]>
9
 *
10
 * @author Qiang Xue <[email protected]>
11
 * @link https://github.com/pradosoft/prado
12
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
13
 */
14
15
namespace Prado;
16
17
use Prado\Collections\TPriorityMap;
18
use Prado\Collections\TWeakCallableCollection;
19
use Prado\Exceptions\TApplicationException;
20
use Prado\Exceptions\TInvalidDataTypeException;
21
use Prado\Exceptions\TInvalidDataValueException;
22
use Prado\Exceptions\TInvalidOperationException;
23
use Prado\Util\IBaseBehavior;
24
use Prado\Util\IBehavior;
25
use Prado\Util\TCallChain;
26
use Prado\Util\IClassBehavior;
27
use Prado\Util\IDynamicMethods;
28
use Prado\Util\TClassBehaviorEventParameter;
29
use Prado\Web\Javascripts\TJavaScriptLiteral;
30
use Prado\Web\Javascripts\TJavaScriptString;
31
32
/**
33
 * TComponent class
34
 *
35
 * TComponent is the base class for all PRADO components.
36
 * TComponent implements the protocol of defining, using properties, behaviors,
37
 * events, dynamic events, and global events.
38
 *
39
 * Properties
40
 *
41
 * A property is defined by a getter method, and/or a setter method.
42
 * Properties can be accessed in the way like accessing normal object members.
43
 * Reading or writing a property will cause the invocation of the corresponding
44
 * getter or setter method, e.g.,
45
 * <code>
46
 * $a=$this->Text;     // equivalent to $a=$this->getText();
47
 * $this->Text='abc';  // equivalent to $this->setText('abc');
48
 * </code>
49
 * The signatures of getter and setter methods are as follows,
50
 * <code>
51
 * // getter, defines a readable property 'Text'
52
 * function getText() { ... }
53
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
54
 * function setText($value) { ... }
55
 * </code>
56
 * Property names are case-insensitive. It is recommended that they are written
57
 * in the format of concatenated words, with the first letter of each word
58
 * capitalized (e.g. DisplayMode, ItemStyle).
59
 *
60
 *
61
 * Javascript Get and Set Properties
62
 *
63
 * Since Prado 3.2 a new class of javascript-friendly properties have been introduced
64
 * to better deal with potential security problems like cross-site scripting issues.
65
 * All the data that gets sent clientside inside a javascript block is now encoded by default.
66
 * Sometimes there's the need to bypass this encoding and be able to send raw javascript code.
67
 * This new class of javascript-friendly properties are identified by their name
68
 * starting with 'js' (case insensitive):
69
 * <code>
70
 * // getter, defines a readable property 'Text'
71
 * function getJsText() { ... }
72
 * // setter, defines a writable property 'Text', with $value being the value to be set to the property
73
 * function setJsText(TJavaScriptLiteral $value) { ... }
74
 * </code>
75
 * Js-friendly properties can be accessed using both their Js-less name and their Js-enabled name:
76
 * <code>
77
 * // set some simple text as property value
78
 * $component->Text = 'text';
79
 * // set some javascript code as property value
80
 * $component->JsText = 'raw javascript';
81
 * </code>
82
 * In the first case, the property value will automatically gets encoded when sent
83
 * clientside inside a javascript block.
84
 * In the second case, the property will be 'marked' as being a safe javascript
85
 * statement and will not be encoded when rendered inside a javascript block.
86
 * This special handling makes use of the {@link TJavaScriptLiteral} class.
87
 *
88
 *
89
 * Object Events
90
 *
91
 * An event is defined by the presence of a method whose name starts with 'on'.
92
 * The event name is the method name and is thus case-insensitive.
93
 * An event can be attached with one or several methods (called event handlers).
94
 * An event can be raised by calling {@link raiseEvent} method, upon which
95
 * the attached event handlers will be invoked automatically in the order they
96
 * are attached to the event. Event handlers must have the following signature,
97
 * <code>
98
 * function eventHandlerFuncName($sender, $param) { ... }
99
 * </code>
100
 * where $sender refers to the object who is responsible for the raising of the event,
101
 * and $param refers to a structure that may contain event-specific information.
102
 * To raise an event (assuming named as 'Click') of a component, use
103
 * <code>
104
 * $component->raiseEvent('OnClick');
105
 * $component->raiseEvent('OnClick', $this, $param);
106
 * </code>
107
 * To attach an event handler to an event, use one of the following ways,
108
 * <code>
109
 * $component->OnClick = $callback;
110
 * $component->OnClick->add($callback);
111
 * $component->attachEventHandler('OnClick', $callback);
112
 * </code>
113
 * The first two ways make use of the fact that $component->OnClick refers to
114
 * the event handler list {@link TWeakCallableCollection} for the 'OnClick' event.
115
 * The variable $callback contains the definition of the event handler that can
116
 * be either:
117
 *
118
 * a string referring to a global function name
119
 * <code>
120
 * $component->OnClick = 'buttonClicked';
121
 * // will cause the following function to be called
122
 * buttonClicked($sender, $param);
123
 * </code>
124
 *
125
 * All types of PHP Callables are supported, such as:
126
 *  - Simple Callback function string, eg. 'my_callback_function'
127
 *  - Static class method call, eg. ['MyClass', 'myCallbackMethod'] and 'MyClass::myCallbackMethod'
128
 *  - Object method call, eg. [$object, 'myCallbackMethod']
129
 *  - Objects implementing __invoke
130
 *  - Closure / anonymous functions
131
 *
132
 * PRADO can accept method names in PRADO namespace as well.
133
 * <code>
134
 * $component->OnClick = [$object, 'buttonClicked'];
135
 * // will cause the following function to be called
136
 * $object->buttonClicked($sender, param);
137
 *
138
 * // the method can also be expressed using the PRADO namespace format
139
 * $component->OnClick = [$object, 'MainContent.SubmitButton.buttonClicked'];
140
 * // will cause the following function to be called
141
 * $object->MainContent->SubmitButton->buttonClicked($sender, $param);
142
 *
143
 * // Closure as an event handler
144
 * $component->OnClick = function ($sender, $param) { ... };
145
 * </code
146
 *
147
 *
148
 * Global and Dynamic Events
149
 *
150
 * With the addition of behaviors, a more expansive event model is needed.  There
151
 * are two new event types (global and dynamic events) as well as a more comprehensive
152
 * behavior model that includes class wide behaviors.
153
 *
154
 * A global event is defined by all events whose name starts with 'fx'.
155
 * The event name is potentially a method name and is thus case-insensitive. All 'fx' events
156
 * are valid as the whole 'fx' event/method space is global in nature. Any object may patch into
157
 * any global event by defining that event as a method. Global events have priorities
158
 * just like 'on' events; so as to be able to order the event execution. Due to the
159
 * nature of all events which start with 'fx' being valid, in effect, every object
160
 * has every 'fx' global event. It is simply an issue of tapping into the desired
161
 * global event.
162
 *
163
 * A global event that starts with 'fx' can be called even if the object does not
164
 * implement the method of the global event.  A call to a non-existing 'fx' method
165
 * will, at minimal, function and return null.  If a method argument list has a first
166
 * parameter, it will be returned instead of null.  This allows filtering and chaining.
167
 * 'fx' methods do not automatically install and uninstall. To install and uninstall an
168
 * object's global event listeners, call the object's {@link listen} and
169
 * {@link unlisten} methods, respectively.  An object may auto-install its global event
170
 * during {@link __construct} by overriding {@link getAutoGlobalListen} and returning true.
171
 *
172
 * As of PHP version 5.3, nulled objects without code references will still continue to persist
173
 * in the global event queue because {@link __destruct} is not automatically called.  In the common
174
 * __destruct method, if an object is listening to global events, then {@link unlisten} is called.
175
 * {@link unlisten} is required to be manually called before an object is
176
 * left without references if it is currently listening to any global events. This includes
177
 * class wide behaviors.  This is corrected in PHP 7.4.0 with WeakReferences and {@link
178
 * TWeakCallableCollection}
179
 *
180
 * An object that contains a method that starts with 'fx' will have those functions
181
 * automatically receive those events of the same name after {@link listen} is called on the object.
182
 *
183
 * An object may listen to a global event without defining an 'fx' method of the same name by
184
 * adding an object method to the global event list.  For example
185
 * <code>
186
 * $component->fxGlobalCheck=$callback;
187
 * $component->fxGlobalCheck->add($callback);
188
 * $component->attachEventHandler('fxGlobalCheck', [$object, 'someMethod']);
189
 * </code>
190
 *
191
 *
192
 * Events between Objects and their behaviors, Dynamic Events
193
 *
194
 * An intra-object/behavior event is defined by methods that start with 'dy'.  Just as with
195
 * 'fx' global events, every object has every dynamic event.  Any call to a method that
196
 * starts with 'dy' will be handled, regardless of whether it is implemented.  These
197
 * events are for communicating with attached behaviors.
198
 *
199
 * Dynamic events can be used in a variety of ways.  They can be used to tell behaviors
200
 * when a non-behavior method is called.  Dynamic events could be used as data filters.
201
 * They could also be used to specify when a piece of code is to be run, eg. should the
202
 * loop process be performed on a particular piece of data.  In this way, some control
203
 * is handed to the behaviors over the process and/or data.
204
 *
205
 * If there are no handlers for an 'fx' or 'dy' event, it will return the first
206
 * parameter of the argument list.  If there are no arguments, these events
207
 * will return null.  If there are handlers an 'fx' method will be called directly
208
 * within the object.  Global 'fx' events are triggered by calling {@link raiseEvent}.
209
 * For dynamic events where there are behaviors that respond to the dynamic events, a
210
 * {@link TCallChain} is developed.  A call chain allows the behavior dynamic event
211
 * implementations to call further implementing behaviors within a chain.
212
 *
213
 * If an object implements {@link IDynamicMethods}, all global and object dynamic
214
 * events will be sent to {@link __dycall}.  In the case of global events, all
215
 * global events will trigger this method.  In the case of behaviors, all undefined
216
 * dynamic events  which are called will be passed through to this method.
217
 *
218
 *
219
 * Behaviors
220
 *
221
 * PRADO TComponent Behaviors is a method to extend a single component or a class
222
 * of components with new properties, methods, features, and fine control over the
223
 * owner object.  Behaviors can be attached to single objects or whole classes
224
 * (or interfaces, parents, and first level traits).
225
 *
226
 * There are two types of behaviors.  There are individual {@link IBehavior} and
227
 * there are class wide {IClassBehavior}.  IBehavior has one owner and IClassBehavior
228
 * can attach to multiple owners at the same time.  IClassBehavior is designed to be
229
 * stateless, like for specific filtering or addition of data.
230
 *
231
 * When a new class implements {@link IClassBehavior} or {@link IBehavior}, or extends
232
 * the PRADO implementations {@link TClassBehavior} and {@link TBehavior}, it may be
233
 * attached to a TComponent by calling the object's {@link attachBehavior}. The
234
 * behaviors associated name can then be used to {@link enableBehavior} or {@link
235
 * disableBehavior} the specific behavior.
236
 *
237
 * All behaviors may be turned on and off via {@link enableBehaviors} and
238
 * {@link disableBehaviors}, respectively.  To check if behaviors are on or off
239
 * a call to {@link getBehaviorsEnabled} will provide the variable.  By default,
240
 * a behavior's event handlers will be removed from events when disabled.
241
 *
242
 * Attaching and detaching whole sets of behaviors is done using
243
 * {@link attachBehaviors} and {@link detachBehaviors}.  {@link clearBehaviors}
244
 * removes all of an object's behaviors.
245
 *
246
 * {@link asa} returns a behavior of a specific name.  {@link isa} is the
247
 * behavior inclusive function that acts as the PHP operator {@link instanceof}.
248
 * A behavior could provide the functionality of a specific class thus causing
249
 * the host object to act similarly to a completely different class.  A behavior
250
 * would then implement {@link IInstanceCheck} to provide the identity of the
251
 * different class.
252
 *
253
 * IClassBehavior are similar to IBehavior except that the class behavior
254
 * attaches to multiple owners, like all the instances of a class.  A class behavior
255
 * will have the object upon which is being called be prepended to the parameter
256
 * list.  This way the object is known across the class behavior implementation.
257
 *
258
 * Class behaviors are attached using {@link attachClassBehavior} and detached
259
 * using {@link detachClassBehavior}.  Class behaviors are important in that
260
 * they will be applied to all new instances of a particular class and all listening
261
 * components as well.  Classes, Class Parents, Interfaces, and first level Traits
262
 * can be attached by class.
263
 * Class behaviors are default behaviors to new instances of a class in and are
264
 * received in {@link __construct}.  Detaching a class behavior will remove the
265
 * behavior from the default set of behaviors created for an object when the object
266
 * is instanced.
267
 *
268
 * Class behaviors are also added to all existing instances via the global 'fx'
269
 * event mechanism.  When a new class behavior is added, the event
270
 * {@link fxAttachClassBehavior} is raised and all existing instances that are
271
 * listening to this global event (primarily after {@link listen} is called)
272
 * will have this new behavior attached.  A similar process is used when
273
 * detaching class behaviors.  Any objects listening to the global 'fx' event
274
 * {@link fxDetachClassBehavior} will have a class behavior removed.
275
 *
276
 * Anonymous Behaviors are supported where the behavior does not have a name or
277
 * the behavior has a numeric for a name.  These cannot be accessed by name because
278
 * their names may be different in each request, for different owners, and possibly,
279
 * though extremely rarely, even the same object between serialization-sleep and
280
 * unserialization-wakeup.
281
 *
282
 * When serializing a component with behaviors, behaviors are saved and restored.
283
 * Named IClassBehavior class behaviors are updated with the current instance
284
 * of the named class behavior rather than replicate it from the wake up. {@link
285
 * __wakeup} will add any new named class behaviors to the unserializing component.
286
 *
287
 * IClassBehaviors can only use one given name for all behaviors except when applied
288
 * anonymously (with no name or a numeric name).
289
 *
290
 *
291
 * Dynamic Intra-Object Behavior Events
292
 *
293
 * Dynamic events start with 'dy'.  This mechanism is used to allow objects
294
 * to communicate with their behaviors directly.  The entire 'dy' event space
295
 * is valid.  All attached, enabled behaviors that implement a dynamic event
296
 * are called when the host object calls the dynamic event.  If there is no
297
 * implementation or behaviors, this returns null when no parameters are
298
 * supplied and will return the first parameter when there is at least one
299
 * parameter in the dynamic event.
300
 * <code>
301
 *	 null == $this->dyBehaviorEvent();
302
 *	 5 == $this->dyBehaviorEvent(5); //when no behaviors implement this dynamic event
303
 * </code>
304
 *
305
 * Dynamic events can be chained together within behaviors to allow for data
306
 * filtering. Dynamic events are implemented within behaviors by defining the
307
 * event as a method.
308
 * <code>
309
 * class TObjectBehavior extends TBehavior {
310
 *     public function dyBehaviorEvent($param1, $callchain) {
311
 *			//Do something, eg:  $param1 += $this->getOwner()->getNumber();
312
 *			return $callchain->dyBehaviorEvent($param1);
313
 *     }
314
 * }
315
 * </code>
316
 * This implementation of a behavior and dynamic event will flow through to the
317
 * next behavior implementing the dynamic event.  The first parameter is always
318
 * return when it is supplied.  Otherwise a dynamic event returns null.
319
 *
320
 * In the case of a class behavior, the object is also prepended to the dynamic
321
 * event.
322
 * <code>
323
 * class TObjectClassBehavior extends TClassBehavior {
324
 *     public function dyBehaviorEvent($hostobject, $param1, $callchain) {
325
 *			//Do something, eg:  $param1 += $hostobject->getNumber();
326
 *			return $callchain->dyBehaviorEvent($param1);
327
 *     }
328
 * }
329
 * </code>
330
 * When calling a dynamic event, only the parameters are passed.  The host object
331
 * and the call chain are built into the framework.
332
 *
333
 *
334
 * Global Event and Dynamic Event Catching
335
 *
336
 * Given that all global 'fx' events and dynamic 'dy' events are valid and
337
 * operational, there is a mechanism for catching events called that are not
338
 * implemented (similar to the built-in PHP method {@link __call}).  When
339
 * a dynamic or global event is called but a behavior does not implement it,
340
 * yet desires to know when an undefined dynamic event is run, the behavior
341
 * implements the interface {@link IDynamicMethods} and method {@link __dycall}.
342
 *
343
 * In the case of dynamic events, {@link __dycall} is supplied with the method
344
 * name and its parameters.  When a global event is raised, via {@link raiseEvent},
345
 * the method is the event name and the parameters are supplied.
346 194
 *
347
 * When implemented, this catch-all mechanism is called for event global event event
348 194
 * when implemented outside of a behavior.  Within a behavior, it will also be called
349 38
 * when the object to which the behavior is attached calls any unimplemented dynamic
350
 * event.  This is the fall-back mechanism for informing a class and/or behavior
351
 * of when an global and/or undefined dynamic event is executed.
352 194
 *
353 194
 * @author Qiang Xue <[email protected]>
354 194
 * @author Brad Anderson <[email protected]>
355 194
 * @since 3.0
356
 * @method void dyClone()
357
 * @method void dyWakeUp()
358 194
 * @method void dyListen(array $globalEvents)
359
 * @method void dyUnlisten(array $globalEvents)
360
 * @method string dyPreRaiseEvent(string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
361
 * @method dyIntraRaiseEventTestHandler(callable $handler, mixed $sender, \Prado\TEventParameter $param, string $name)
362
 * @method bool dyIntraRaiseEventPostHandler(string $name, mixed $sender, \Prado\TEventParameter $param, callable $handler, $response)
363
 * @method array dyPostRaiseEvent(array $responses, string $name, mixed $sender, \Prado\TEventParameter $param, null|numeric $responsetype, null|function $postfunction)
364
 * @method string dyEvaluateExpressionFilter(string $statements)
365
 * @method string dyEvaluateStatementsFilter(string $statements)
366
 * @method dyCreatedOnTemplate(\Prado\TComponent $parent)
367
 * @method void dyAddParsedObject(\Prado\TComponent|string $object)
368
 * @method void dyAttachBehavior(string $name, IBaseBehavior $behavior)
369
 * @method void dyDetachBehavior(string $name, IBaseBehavior $behavior)
370
 * @method void dyEnableBehavior(string $name, IBaseBehavior $behavior)
371
 * @method void dyDisableBehavior(string $name, IBaseBehavior $behavior)
372
 * @method void dyEnableBehaviors()
373 183
 * @method void dyDisableBehaviors()
374
 */
375 183
class TComponent
376
{
377
	/**
378
	 * @var array event handler lists
379
	 */
380
	protected $_e = [];
381
382
	/**
383
	 * @var bool if listening is enabled.  Automatically turned on or off in
384
	 * constructor according to {@link getAutoGlobalListen}.  Default false, off
385
	 */
386 620
	protected $_listeningenabled = false;
387
388 620
	/**
389
	 * @var array static registered global event handler lists
390
	 */
391 620
	private static $_ue = [];
392
393
	/**
394
	 * @var bool if object behaviors are on or off.  default true, on
395
	 */
396
	protected $_behaviorsenabled = true;
397
398
	/**
399 38
	 * @var TPriorityMap list of object behaviors
400
	 */
401 38
	protected $_m;
402
403
	/**
404
	 * @var array static global class behaviors, these behaviors are added upon instantiation of a class
405
	 */
406
	private static $_um = [];
407
408
409
	/**
410
	 * @const string the name of the global {@link raiseEvent} listener
411 194
	 */
412
	public const GLOBAL_RAISE_EVENT_LISTENER = 'fxGlobalListener';
413 194
414 194
415 194
	/**
416 192
	 * The common __construct.
417
	 * If desired by the new object, this will auto install and listen to global event functions
418 194
	 * as defined by the object via 'fx' methods. This also attaches any predefined behaviors.
419 194
	 * This function installs all class behaviors in a class hierarchy from the deepest subclass
420
	 * through each parent to the top most class, TComponent.
421 1
	 */
422
	public function __construct()
423
	{
424
		if ($this->getAutoGlobalListen()) {
425
			$this->listen();
426
		}
427
428
		$classes = $this->getClassHierarchy(true);
429
		array_pop($classes);
430
		foreach ($classes as $class) {
431
			if (isset(self::$_um[$class])) {
432
				$this->attachBehaviors(self::$_um[$class], true);
433
			}
434
		}
435
	}
436
437
	/**
438
	 * The common __clone magic method from PHP's "clone".
439 38
	 * This reattaches the behaviors to the cloned object.
440
	 * IBehavior objects are cloned, IClassBehaviors are not.
441 38
	 * Clone object events are scrubbed of the old object behaviors' events.
442
	 * To finalize the behaviors, dyClone is raised.
443
	 * @since 4.2.3
444
	 */
445 38
	public function __clone()
446
	{
447 38
		foreach ($this->_e as $event => $handlers) {
448 38
			$this->_e[$event] = clone $handlers;
449
		}
450
		$behaviorArray = array_values(($this->_m !== null) ? $this->_m->toArray() : []);
451 38
		if (count($behaviorArray) && count($this->_e)) {
452 3
			$behaviorArray = array_combine(array_map('spl_object_id', $behaviorArray), $behaviorArray);
453 3
			foreach ($this->_e as $event => $handlers) {
454
				foreach ($handlers->toArray() as $handler) {
455
					$a = is_array($handler);
456 38
					if ($a && array_key_exists(spl_object_id($handler[0]), $behaviorArray) || !$a && array_key_exists(spl_object_id($handler), $behaviorArray)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($a && array_key_exists(...ndler), $behaviorArray), Probably Intended Meaning: $a && (array_key_exists(...dler), $behaviorArray))
Loading history...
457
						$handlers->remove($handler);
458 38
					}
459
				}
460 38
			}
461
		}
462
		if ($this->_m !== null) {
463
			$behaviors = $this->_m;
464
			$this->_m = new TPriorityMap();
465
			foreach ($behaviors->getPriorities() as $priority) {
466
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
467
					if ($behavior instanceof IBehavior) {
468
						$behavior = clone $behavior;
469
					}
470
					$this->attachBehavior($name, $behavior, $priority);
471
				}
472
			}
473
		}
474
		$this->callBehaviorsMethod('dyClone', $return);
475
	}
476 38
477
	/**
478 38
	 * The common __wakeup magic method from PHP's "unserialize".
479 3
	 * This reattaches the behaviors to the reconstructed object.
480
	 * Any global class behaviors are used rather than their unserialized copy.
481
	 * Any global behaviors not found in the object will be added.
482 38
	 * To finalize the behaviors, dyWakeUp is raised.
483
	 * If a TModule needs to add events to an object during unserialization,
484 38
	 * the module can use a small IClassBehavior [implementing dyWakeUp]
485 38
	 * (adding the event[s]) attached to the class with {@link
486
	 * attachClassBehavior} prior to unserialization.
487
	 * @since 4.2.3
488 38
	 */
489 3
	public function __wakeup()
490 3
	{
491
		$classes = $this->getClassHierarchy(true);
492
		array_pop($classes);
493 38
		$classBehaviors = [];
494
		if ($this->_m !== null) {
495 38
			$behaviors = $this->_m;
496
			$this->_m = new TPriorityMap();
497 38
			foreach ($behaviors->getPriorities() as $priority) {
498
				foreach ($behaviors->itemsAtPriority($priority) as $name => $behavior) {
499
					if ($behavior instanceof IClassBehavior && !is_numeric($name)) {
500
						//Replace class behaviors with their current instances, if they exist.
501
						foreach ($classes as $class) {
502
							if (isset(self::$_um[$class]) && array_key_exists($name, self::$_um[$class])) {
503
								$behavior = self::$_um[$class][$name]->getBehavior();
504 4
								break;
505
							}
506 4
						}
507
					}
508
					$classBehaviors[$name] = $name;
509
					$this->attachBehavior($name, $behavior, $priority);
510
				}
511
			}
512
		}
513
		foreach ($classes as $class) {
514
			if (isset(self::$_um[$class])) {
515
				foreach (self::$_um[$class] as $name => $behavior) {
516
					if(is_numeric($name)) {
517
						continue;
518
					}
519
					if (!array_key_exists($name, $classBehaviors)) {
520
						$this->attachBehaviors([$name => $behavior], true);
521
					}
522
				}
523
			}
524
		}
525
		$this->callBehaviorsMethod('dyWakeUp', $return);
526
	}
527
528
529
	/**
530
	 * Tells TComponent whether or not to automatically listen to global events.
531
	 * Defaults to false because PHP variable cleanup is affected if this is true.
532
	 * When unsetting a variable that is listening to global events, {@link unlisten}
533 211
	 * must explicitly be called when cleaning variables allocation or else the global
534
	 * event registry will contain references to the old object. This is true for PHP 5.4
535 211
	 *
536 211
	 * Override this method by a subclass to change the setting.  When set to true, this
537 4
	 * will enable {@link __construct} to call {@link listen}.
538 4
	 *
539 4
	 * @return bool whether or not to auto listen to global events during {@link __construct}, default false
540
	 */
541
	public function getAutoGlobalListen()
542
	{
543
		return false;
544
	}
545
546
547
	/**
548 4
	 * The common __destruct
549
	 * When listening, this unlistens from the global event routines.  It also detaches
550
	 * all the behaviors so they can clean up, eg remove handlers.
551
	 *
552
	 * Prior to PHP 7.4, when listening, unlisten must be manually called for objects
553 211
	 * to destruct because circular references will prevent the __destruct process.
554 27
	 */
555 27
	public function __destruct()
556 27
	{
557 24
		$this->clearBehaviors();
558 3
		if ($this->_listeningenabled) {
559 3
			$this->unlisten();
560 2
		}
561
	}
562 24
563
564
	/**
565 27
	 * This utility function is a private array filter method.  The array values
566 27
	 * that start with 'fx' are filtered in.
567
	 * @param mixed $name
568
	 */
569 8
	private function filter_prado_fx($name)
570 8
	{
571 8
		return strncasecmp($name, 'fx', 2) === 0;
572 3
	}
573
574 8
575
	/**
576
	 * This returns an array of the class name and the names of all its parents.  The base object last,
577
	 * {@link TComponent}, and the deepest subclass is first.
578
	 * @param bool $lowercase optional should the names be all lowercase true/false
579
	 * @return string[] array of strings being the class hierarchy of $this.
580 211
	 */
581 211
	public function getClassHierarchy($lowercase = false)
582 6
	{
583
		static $_classhierarchy = [];
584 211
		$class = $this::class;
585
		if (isset($_classhierarchy[$class]) && isset($_classhierarchy[$class][$lowercase ? 1 : 0])) {
586
			return $_classhierarchy[$class][$lowercase ? 1 : 0];
587
		}
588 4
		$classes = [array_values(class_implements($class))];
589 4
		do {
590
			$classes[] = array_values(class_uses($class));
591
			$classes[] = [$class];
592
		} while ($class = get_parent_class($class));
593
		$classes = array_merge(...$classes);
594
		if ($lowercase) {
595
			$classes = array_map('strtolower', $classes);
596
		}
597
		$_classhierarchy[$class] ??= [];
598
		$_classhierarchy[$class][$lowercase ? 1 : 0] = $classes;
599
600
		return $classes;
601
	}
602
603
	/**
604
	 * This caches the 'fx' events for classes.
605
	 * @param object $class
606
	 * @return string[] fx events from a specific class
607
	 */
608
	protected function getClassFxEvents($class)
609
	{
610
		static $_classfx = [];
611
		$className = $class::class;
612
		if (isset($_classfx[$className])) {
613
			return $_classfx[$className];
614
		}
615
		$fx = array_filter(get_class_methods($class), [$this, 'filter_prado_fx']);
616
		$_classfx[$className] = $fx;
617 95
		return $fx;
618
	}
619 95
620
	/**
621 84
	 * This adds an object's fx event handlers into the global broadcaster to listen into any
622 25
	 * broadcast global events called through {@link raiseEvent}
623
	 *
624 2
	 * Behaviors may implement the function:
625 23
	 * <code>
626
	 *	public function dyListen($globalEvents[, ?TCallChain $chain = null]) {
627 13
	 * 		$this->listen($globalEvents); //eg
628 13
	 *      if ($chain)
629 12
	 *          $chain->dyUnlisten($globalEvents);
630
	 * }
631 13
	 * </code>
632 14
	 * to be executed when listen is called.  All attached behaviors are notified through dyListen.
633
	 *
634 3
	 * @return numeric the number of global events that were registered to the global event registry
0 ignored issues
show
Bug introduced by
The type Prado\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
635 3
	 */
636 1
	public function listen()
637
	{
638 3
		if ($this->_listeningenabled) {
639 13
			return;
640
		}
641 13
642 9
		$fx = $this->getClassFxEvents($this);
643 7
644 4
		foreach ($fx as $func) {
645 4
			$this->getEventHandlers($func)->add([$this, $func]);
646 4
		}
647 4
648
		if (is_a($this, IDynamicMethods::class)) {
649
			$this->attachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
650
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
651
		}
652 4
653
		$this->_listeningenabled = true;
654
655
		$this->callBehaviorsMethod('dyListen', $return, $fx);
656
657
		return count($fx);
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($fx) returns the type integer which is incompatible with the documented return type Prado\numeric.
Loading history...
658
	}
659
660
	/**
661
	 * this removes an object's fx events from the global broadcaster
662
	 *
663
	 * Behaviors may implement the function:
664
	 * <code>
665
	 *	public function dyUnlisten($globalEvents[, ?TCallChain $chain = null]) {
666
	 * 		$this->behaviorUnlisten(); //eg
667
	 *      if ($chain)
668
	 *          $chain->dyUnlisten($globalEvents);
669
	 * }
670 77
	 * </code>
671
	 * to be executed when listen is called.  All attached behaviors are notified through dyUnlisten.
672 77
	 *
673 76
	 * @return numeric the number of global events that were unregistered from the global event registry
674 2
	 */
675
	public function unlisten()
676 76
	{
677 5
		if (!$this->_listeningenabled) {
678 1
			return;
679 1
		}
680
681 1
		$fx = $this->getClassFxEvents($this);
682 4
683 3
		foreach ($fx as $func) {
684 4
			$this->detachEventHandler($func, [$this, $func]);
685 4
		}
686 4
687 4
		if (is_a($this, IDynamicMethods::class)) {
688 4
			$this->detachEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER, [$this, '__dycall']);
689 4
			array_push($fx, TComponent::GLOBAL_RAISE_EVENT_LISTENER);
690 4
		}
691
692
		$this->_listeningenabled = false;
693 4
694 4
		$this->callBehaviorsMethod('dyUnlisten', $return, $fx);
695
696
		return count($fx);
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($fx) returns the type integer which is incompatible with the documented return type Prado\numeric.
Loading history...
697
	}
698 1
699 1
	/**
700
	 * Gets the state of listening to global events
701 1
	 * @return bool is Listening to global broadcast enabled
702
	 */
703
	public function getListeningToGlobalEvents()
704
	{
705
		return $this->_listeningenabled;
706
	}
707
708
709
	/**
710
	 * Calls a method.
711
	 * Do not call this method directly. This is a PHP magic method that we override
712
	 * to allow behaviors, dynamic events (intra-object/behavior events),
713
	 * undefined dynamic and global events, and
714
	 * to allow using the following syntax to call a property setter or getter.
715
	 * <code>
716
	 * $this->getPropertyName($value); // if there's a $this->getjsPropertyName() method
717 5
	 * $this->setPropertyName($value); // if there's a $this->setjsPropertyName() method
718
	 * </code>
719 5
	 *
720 5
	 * Additional object behaviors override class behaviors.
721 3
	 * dynamic and global events do not fail even if they aren't implemented.
722 2
	 * Any intra-object/behavior dynamic events that are not implemented by the behavior
723 1
	 * return the first function paramater or null when no parameters are specified.
724 1
	 *
725 1
	 * @param string $method method name that doesn't exist and is being called on the object
726 1
	 * @param mixed $args method parameters
727 1
	 * @throws TInvalidOperationException If the property is not defined or read-only or
728 1
	 * 		method is undefined
729 1
	 * @return mixed result of the method call, or false if 'fx' or 'dy' function but
730 1
	 *		is not found in the class, otherwise it runs
731 1
	 */
732
	public function __call($method, $args)
733 1
	{
734 1
		$getset = substr($method, 0, 3);
735 1
		if (($getset == 'get') || ($getset == 'set')) {
736
			$propname = substr($method, 3);
737
			$jsmethod = $getset . 'js' . $propname;
738
			if (Prado::method_visible($this, $jsmethod)) {
739 1
				if (count($args) > 0) {
740
					if ($args[0] && !($args[0] instanceof TJavaScriptString)) {
741
						$args[0] = new TJavaScriptString($args[0]);
742
					}
743
				}
744
				return $this->$jsmethod(...$args);
745
			}
746
747
			if (($getset == 'set') && Prado::method_visible($this, 'getjs' . $propname)) {
748
				throw new TInvalidOperationException('component_property_readonly', $this::class, $method);
749
			}
750
		}
751
		if ($this->callBehaviorsMethod($method, $return, ...$args)) {
752
			return $return;
753 3
		}
754
755 3
		// don't throw an exception for __magicMethods() or any other weird methods natively implemented by php
756 2
		if (!method_exists($this, $method)) {
757 3
			throw new TApplicationException('component_method_undefined', $this::class, $method);
758 1
		}
759 2
	}
760 2
761 2
762 1
	/**
763 2
	 * Returns a property value or an event handler list by property or event name.
764 2
	 * Do not call this method. This is a PHP magic method that we override
765 1
	 * to allow using the following syntax to read a property:
766
	 * <code>
767 2
	 * $value = $component->PropertyName;
768 2
	 * $value = $component->jsPropertyName; // return JavaScript literal
769 2
	 * </code>
770 2
	 * and to obtain the event handler list for an event,
771 2
	 * <code>
772
	 * $eventHandlerList = $component->EventName;
773
	 * </code>
774 2
	 * This will also return the global event handler list when specifing an 'fx'
775 2
	 * event,
776
	 * <code>
777
	 * $globalEventHandlerList = $component->fxEventName;
778 1
	 * </code>
779 1
	 * When behaviors are enabled, this will return the behavior of a specific
780
	 * name, a property of a behavior, or an object 'on' event defined by the behavior.
781 3
	 * @param string $name the property name or the event name
782
	 * @throws TInvalidOperationException if the property/event is not defined.
783
	 * @return mixed the property value or the event handler list as {@link TWeakCallableCollection}
784
	 */
785
	public function __get($name)
786
	{
787
		if (Prado::method_visible($this, $getter = 'get' . $name)) {
788
			// getting a property
789
			return $this->$getter();
790 1
		} elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) {
791
			// getting a javascript property
792 1
			return (string) $this->$jsgetter();
793
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
794
			// getting an event (handler list)
795
			$name = strtolower($name);
796
			if (!isset($this->_e[$name])) {
797
				$this->_e[$name] = new TWeakCallableCollection();
798
			}
799
			return $this->_e[$name];
800
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
801
			// getting a global event (handler list)
802
			$name = strtolower($name);
803
			if (!isset(self::$_ue[$name])) {
804 9
				self::$_ue[$name] = new TWeakCallableCollection();
805
			}
806 9
			return self::$_ue[$name];
807 6
		} elseif ($this->getBehaviorsEnabled()) {
808 8
			// getting a behavior property/event (handler list)
809 2
			$name = strtolower($name);
810 2
			if (isset($this->_m[$name])) {
811 2
				return $this->_m[$name];
812
			} elseif ($this->_m !== null) {
813
				foreach ($this->_m->toArray() as $behavior) {
814
					if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
815 8
						return $behavior->$name;
816
					}
817
				}
818
			}
819
		}
820
		throw new TInvalidOperationException('component_property_undefined', $this::class, $name);
821
	}
822
823
	/**
824
	 * Sets value of a component property.
825
	 * Do not call this method. This is a PHP magic method that we override
826
	 * to allow using the following syntax to set a property or attach an event handler.
827 11
	 * <code>
828
	 *    $this->PropertyName = $value;
829 11
	 *    $this->jsPropertyName = $value; // $value will be treated as a JavaScript literal
830 8
	 *    $this->EventName = $handler;
831 7
	 *    $this->fxEventName = $handler; //global event listener
832 2
	 *    $this->EventName = function($sender, $param) {...};
833 2
	 * </code>
834 2
	 * When behaviors are enabled, this will also set a behaviors properties and events.
835
	 * @param string $name the property name or event name
836
	 * @param mixed $value the property value or event handler
837
	 * @throws TInvalidOperationException If the property is not defined or read-only.
838 7
	 */
839
	public function __set($name, $value)
840
	{
841
		if (Prado::method_visible($this, $setter = 'set' . $name)) {
842
			if (strncasecmp($name, 'js', 2) === 0 && $value && !($value instanceof TJavaScriptLiteral)) {
843
				$value = new TJavaScriptLiteral($value);
844
			}
845
			return $this->$setter($value);
846
		} elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) {
847
			if ($value && !($value instanceof TJavaScriptString)) {
848
				$value = new TJavaScriptString($value);
849
			}
850
			return $this->$jssetter($value);
851 4
		} elseif ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0) {
852
			return $this->attachEventHandler($name, $value);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->attachEventHandler($name, $value) targeting Prado\TComponent::attachEventHandler() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
853 4
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
854 4
			$sets = 0;
855 4
			foreach ($this->_m->toArray() as $behavior) {
856
				if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canSetProperty($name) || $behavior->hasEvent($name))) {
857 4
					$behavior->$name = $value;
858
					$sets++;
859
				}
860
			}
861
			if ($sets) {
862
				return $value;
863
			}
864
		}
865
866
		if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) {
867
			throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
868
		} else {
869
			throw new TInvalidOperationException('component_property_undefined', $this::class, $name);
870 2
		}
871
	}
872 2
873 2
	/**
874 1
	 * Checks if a property value is null, there are no events in the object
875
	 * event list or global event list registered under the name, and, if
876 2
	 * behaviors are enabled,
877 2
	 * Do not call this method. This is a PHP magic method that we override
878
	 * to allow using isset() to detect if a component property is set or not.
879 2
	 * This also works for global events.  When behaviors are enabled, it
880 2
	 * will check for a behavior of the specified name, and also check
881
	 * the behavior for events and properties.
882
	 * @param string $name the property name or the event name
883
	 * @since 3.2.3
884
	 */
885
	public function __isset($name)
886
	{
887
		if (Prado::method_visible($this, $getter = 'get' . $name)) {
888
			return $this->$getter() !== null;
889
		} elseif (Prado::method_visible($this, $jsgetter = 'getjs' . $name)) {
890
			return $this->$jsgetter() !== null;
891
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
892
			$name = strtolower($name);
893
			return isset($this->_e[$name]) && $this->_e[$name]->getCount();
894 176
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
895
			$name = strtolower($name);
896 176
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount();
897 176
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
898 3
			$name = strtolower($name);
899 3
			if (isset($this->_m[$name])) {
900 3
				return true;
901 3
			}
902
			foreach ($this->_m->toArray() as $behavior) {
903
				if ($behavior->getEnabled() && (property_exists($behavior, $name) || $behavior->canGetProperty($name) || $behavior->hasEvent($name))) {
904
					return isset($behavior->$name);
905 3
				}
906
			}
907
		}
908
		return false;
909
	}
910
911
	/**
912
	 * Sets a component property to be null.  Clears the object or global
913
	 * events. When enabled, loops through all behaviors and unsets the
914
	 * property or event.
915 211
	 * Do not call this method. This is a PHP magic method that we override
916
	 * to allow using unset() to set a component property to be null.
917 211
	 * @param string $name the property name or the event name
918 211
	 * @throws TInvalidOperationException if the property is read only.
919 211
	 * @since 3.2.3
920
	 */
921 178
	public function __unset($name)
922 54
	{
923 177
		if (Prado::method_visible($this, $setter = 'set' . $name)) {
924 3
			$this->$setter(null);
925 3
		} elseif (Prado::method_visible($this, $jssetter = 'setjs' . $name)) {
926 3
			$this->$jssetter(null);
927
		} elseif (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
928
			$this->_e[strtolower($name)]->clear();
929
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
930
			$this->getEventHandlers($name)->remove([$this, $name]);
931 176
		} elseif ($this->_m !== null && $this->_m->getCount() > 0 && $this->getBehaviorsEnabled()) {
932
			$name = strtolower($name);
933
			if (isset($this->_m[$name])) {
934
				$this->detachBehavior($name);
935
			} else {
936
				$unset = 0;
937
				foreach ($this->_m->toArray() as $behavior) {
938
					if ($behavior->getEnabled()) {
939
						unset($behavior->$name);
940
						$unset++;
941 92
					}
942
				}
943 92
				if (!$unset && Prado::method_visible($this, 'get' . $name)) {
944 63
					throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
945 63
				}
946 14
			}
947
		} elseif (Prado::method_visible($this, 'get' . $name)) {
948 63
			throw new TInvalidOperationException('component_property_readonly', $this::class, $name);
949 38
		}
950 38
	}
951 38
952 5
	/**
953
	 * Determines whether a property is defined.
954 38
	 * A property is defined if there is a getter or setter method
955 6
	 * defined in the class. Note, property names are case-insensitive.
956 6
	 * @param string $name the property name
957 6
	 * @return bool whether the property is defined
958 6
	 */
959
	public function hasProperty($name)
960
	{
961
		return $this->canGetProperty($name) || $this->canSetProperty($name);
962 4
	}
963
964
	/**
965
	 * Determines whether a property can be read.
966
	 * A property can be read if the class has a getter method
967
	 * for the property name. Note, property name is case-insensitive.
968
	 * This also checks for getjs.  When enabled, it loops through all
969
	 * active behaviors for the get property when undefined by the object.
970
	 * @param string $name the property name
971
	 * @return bool whether the property can be read
972
	 */
973
	public function canGetProperty($name)
974
	{
975
		if (Prado::method_visible($this, 'get' . $name) || Prado::method_visible($this, 'getjs' . $name)) {
976
			return true;
977
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
978
			foreach ($this->_m->toArray() as $behavior) {
979
				if ($behavior->getEnabled() && $behavior->canGetProperty($name)) {
980
					return true;
981
				}
982
			}
983
		}
984
		return false;
985
	}
986
987
	/**
988
	 * Determines whether a property can be set.
989
	 * A property can be written if the class has a setter method
990
	 * for the property name. Note, property name is case-insensitive.
991
	 * This also checks for setjs.  When enabled, it loops through all
992
	 * active behaviors for the set property when undefined by the object.
993
	 * @param string $name the property name
994
	 * @return bool whether the property can be written
995
	 */
996
	public function canSetProperty($name)
997
	{
998
		if (Prado::method_visible($this, 'set' . $name) || Prado::method_visible($this, 'setjs' . $name)) {
999
			return true;
1000
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1001
			foreach ($this->_m->toArray() as $behavior) {
1002
				if ($behavior->getEnabled() && $behavior->canSetProperty($name)) {
1003
					return true;
1004
				}
1005
			}
1006
		}
1007
		return false;
1008
	}
1009
1010 44
	/**
1011
	 * Evaluates a property path.
1012 44
	 * A property path is a sequence of property names concatenated by '.' character.
1013 44
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
1014
	 * 'Parent' property value (which should be a component also).
1015
	 * When a property is not defined by an object, this also loops through all
1016
	 * active behaviors of the object.
1017
	 * @param string $path property path
1018
	 * @return mixed the property path value
1019
	 */
1020
	public function getSubProperty($path)
1021
	{
1022
		$object = $this;
1023
		foreach (explode('.', $path) as $property) {
1024
			$object = $object->$property;
1025 38
		}
1026
		return $object;
1027 38
	}
1028
1029 38
	/**
1030 38
	 * Sets a value to a property path.
1031 1
	 * A property path is a sequence of property names concatenated by '.' character.
1032
	 * For example, 'Parent.Page' refers to the 'Page' property of the component's
1033
	 * 'Parent' property value (which should be a component also).
1034 2
	 * When a property is not defined by an object, this also loops through all
1035
	 * active behaviors of the object.
1036
	 * @param string $path property path
1037
	 * @param mixed $value the property path value
1038
	 */
1039
	public function setSubProperty($path, $value)
1040
	{
1041
		$object = $this;
1042
		if (($pos = strrpos($path, '.')) === false) {
1043
			$property = $path;
1044
		} else {
1045
			$object = $this->getSubProperty(substr($path, 0, $pos));
1046
			$property = substr($path, $pos + 1);
1047
		}
1048
		$object->$property = $value;
1049
	}
1050
1051
	/**
1052
	 * Calls a method on a component's behaviors.  When the method is a
1053
	 * dynamic event, it is raised with all the behaviors.  When a class implements
1054
	 * a dynamic event (eg. for patching), the class can customize raising the
1055
	 * dynamic event with the classes behaviors using this method.
1056
	 * Dynamic [dy] and global [fx] events call {@link __dycall} when $this
1057
	 * implements IDynamicMethods.  Finally, this catches all unexecuted
1058
	 * Dynamic [dy] and global [fx] events and returns the first $args parameter;
1059
	 * acting as a passthrough (filter) of the first $args parameter. In dy/fx methods,
1060
	 * there can be no $args parameters, the first parameter used as a pass through
1061
	 * filter, or act as a return variable with the first $args parameter being
1062
	 * the default return value.
1063
	 * @param string $method The method being called or dynamic/global event being raised.
1064
	 * @param mixed &$return The return value.
1065
	 * @param array $args The arguments to the method being called.
1066
	 * @return bool Was the method handled.
1067
	 * @since 4.2.3
1068
	 */
1069
	public function callBehaviorsMethod($method, &$return, ...$args): bool
1070
	{
1071
		if ($this->_m !== null && $this->getBehaviorsEnabled()) {
1072
			if (strncasecmp($method, 'dy', 2) === 0) {
1073
				if ($callchain = $this->getCallChain($method, ...$args)) {
1074
					$return = $callchain->call(...$args);
1075
					return true;
1076
				}
1077
			} else {
1078
				foreach ($this->_m->toArray() as $behavior) {
1079
					if ($behavior->getEnabled() && Prado::method_visible($behavior, $method)) {
1080
						if ($behavior instanceof IClassBehavior) {
1081
							array_unshift($args, $this);
1082
						}
1083
						$return = $behavior->$method(...$args);
1084
						return true;
1085
					}
1086
				}
1087
			}
1088
		}
1089
		if (strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1090
			if ($this instanceof IDynamicMethods) {
1091
				$return = $this->__dycall($method, $args);
0 ignored issues
show
Bug introduced by
The method __dycall() does not exist on Prado\TComponent. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1091
				/** @scrutinizer ignore-call */ 
1092
    $return = $this->__dycall($method, $args);
Loading history...
1092
				return true;
1093
			}
1094
			$return = $args[0] ?? null;
1095
			return true;
1096
		}
1097
		return false;
1098
	}
1099
1100
	/**
1101
	 * This gets the chain of methods implemented by attached and enabled behaviors.
1102
	 * This method disregards the {behaviorsEnabled
1103
	 * @param string $method The name of the behaviors method being chained.
1104
	 * @param array $args The arguments to the behaviors method being chained.
1105
	 * @return ?TCallChain The chain of methods implemented by behaviors or null when
1106
	 *   there are no methods to call.
1107
	 * @since 4.2.3
1108
	 */
1109 182
	protected function getCallChain($method, ...$args): ?TCallChain
1110
	{
1111 182
		$classArgs = $callchain = null;
1112 182
		foreach ($this->_m->toArray() as $behavior) {
1113 1
			if ($behavior->getEnabled() && (Prado::method_visible($behavior, $method) || ($behavior instanceof IDynamicMethods))) {
1114 1
				if ($classArgs === null) {
1115
					$classArgs = $args;
1116
					array_unshift($classArgs, $this);
1117 182
				}
1118 181
				if (!$callchain) {
1119
					$callchain = new TCallChain($method);
1120
				}
1121 182
				$callchain->addCall([$behavior, $method], ($behavior instanceof IClassBehavior) ? $classArgs : $args);
1122 182
			}
1123
		}
1124 182
		return $callchain;
1125
	}
1126 182
1127 60
	/**
1128 60
	 * Determines whether a method is defined. When behaviors are enabled, this
1129 60
	 * will loop through all enabled behaviors checking for the method as well.
1130 1
	 * Nested behaviors within behaviors are not supported but the nested behavior can
1131 1
	 * affect the primary behavior like any behavior affects their owner.
1132
	 * Note, method name are case-insensitive.
1133 60
	 * @param string $name the method name
1134 60
	 * @return bool
1135 59
	 * @since 4.2.2
1136
	 */
1137
	public function hasMethod($name)
1138
	{
1139 59
		if (Prado::method_visible($this, $name) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
1140
			return true;
1141
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1142
			foreach ($this->_m->toArray() as $behavior) {
1143
				//Prado::method_visible($behavior, $name) rather than $behavior->hasMethod($name) b/c only one layer is supported, @4.2.2
1144
				if ($behavior->getEnabled() && Prado::method_visible($behavior, $name)) {
1145
					return true;
1146
				}
1147
			}
1148
		}
1149
		return false;
1150
	}
1151
1152
	/**
1153
	 * Determines whether an event is defined.
1154
	 * An event is defined if the class has a method whose name is the event name
1155 59
	 * prefixed with 'on', 'fx', or 'dy'.
1156 59
	 * Every object responds to every 'fx' and 'dy' event as they are in a universally
1157 59
	 * accepted event space.  'on' event must be declared by the object.
1158
	 * When enabled, this will loop through all active behaviors for 'on' events
1159
	 * defined by the behavior.
1160 59
	 * Note, event name is case-insensitive.
1161 1
	 * @param string $name the event name
1162 1
	 * @return bool
1163
	 */
1164 59
	public function hasEvent($name)
1165 59
	{
1166 1
		if ((strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) || strncasecmp($name, 'fx', 2) === 0 || strncasecmp($name, 'dy', 2) === 0) {
1167
			return true;
1168 59
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1169
			foreach ($this->_m->toArray() as $behavior) {
1170
				if ($behavior->getEnabled() && $behavior->hasEvent($name)) {
1171 59
					return true;
1172
				}
1173
			}
1174
		}
1175
		return false;
1176
	}
1177
1178 59
	/**
1179
	 * Checks if an event has any handlers.  This function also checks through all
1180 59
	 * the behaviors for 'on' events when behaviors are enabled.
1181 1
	 * 'dy' dynamic events are not handled by this function.
1182
	 * @param string $name the event name
1183
	 * @return bool whether an event has been attached one or several handlers
1184 59
	 */
1185 2
	public function hasEventHandler($name)
1186
	{
1187 58
		$name = strtolower($name);
1188
		if (strncasecmp($name, 'fx', 2) === 0) {
1189
			return isset(self::$_ue[$name]) && self::$_ue[$name]->getCount() > 0;
1190 59
		} else {
1191 60
			if (isset($this->_e[$name]) && $this->_e[$name]->getCount() > 0) {
1192
				return true;
1193
			} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1194 173
				foreach ($this->_m->toArray() as $behavior) {
1195 1
					if ($behavior->getEnabled() && $behavior->hasEventHandler($name)) {
1196
						return true;
1197
					}
1198 182
				}
1199 181
			}
1200
		}
1201
		return false;
1202 182
	}
1203
1204 182
	/**
1205
	 * Returns the list of attached event handlers for an 'on' or 'fx' event.   This function also
1206
	 * checks through all the behaviors for 'on' event lists when behaviors are enabled.
1207
	 * @param mixed $name
1208
	 * @throws TInvalidOperationException if the event is not defined
1209
	 * @return TWeakCallableCollection list of attached event handlers for an event
1210
	 */
1211
	public function getEventHandlers($name)
1212
	{
1213
		if (strncasecmp($name, 'on', 2) === 0 && method_exists($this, $name)) {
1214
			$name = strtolower($name);
1215
			if (!isset($this->_e[$name])) {
1216
				$this->_e[$name] = new TWeakCallableCollection();
1217
			}
1218
			return $this->_e[$name];
1219
		} elseif (strncasecmp($name, 'fx', 2) === 0) {
1220
			$name = strtolower($name);
1221
			if (!isset(self::$_ue[$name])) {
1222
				self::$_ue[$name] = new TWeakCallableCollection();
1223
			}
1224 2
			return self::$_ue[$name];
1225
		} elseif ($this->_m !== null && $this->getBehaviorsEnabled()) {
1226 2
			foreach ($this->_m->toArray() as $behavior) {
1227
				if ($behavior->getEnabled() && $behavior->hasEvent($name)) {
1228 2
					return $behavior->getEventHandlers($name);
1229
				}
1230
			}
1231 2
		}
1232 1
		throw new TInvalidOperationException('component_event_undefined', $this::class, $name);
1233 1
	}
1234
1235
	/**
1236
	 * Attaches an event handler to an event.
1237
	 *
1238
	 * The handler must be a valid PHP callback, i.e., a string referring to
1239
	 * a global function name, or an array containing two elements with
1240
	 * the first element being an object and the second element a method name
1241
	 * of the object. In Prado, you can also use method path to refer to
1242
	 * an event handler. For example, array($object,'Parent.buttonClicked')
1243
	 * uses a method path that refers to the method $object->Parent->buttonClicked(...).
1244
	 *
1245
	 * The event handler must be of the following signature,
1246
	 * <code>
1247
	 * function handlerName($sender, $param) {}
1248
	 * function handlerName($sender, $param, $name) {}
1249
	 * </code>
1250
	 * where $sender represents the object that raises the event,
1251
	 * and $param is the event parameter. $name refers to the event name
1252
	 * being handled.
1253
	 *
1254 2
	 * This is a convenient method to add an event handler.
1255
	 * It is equivalent to {@link getEventHandlers}($name)->add($handler).
1256 2
	 * For complete management of event handlers, use {@link getEventHandlers}
1257
	 * to get the event handler list first, and then do various
1258 2
	 * {@link TWeakCallableCollection} operations to append, insert or remove
1259 2
	 * event handlers. You may also do these operations like
1260
	 * getting and setting properties, e.g.,
1261
	 * <code>
1262 2
	 *    $component->OnClick[] = array($object,'buttonClicked');
1263 2
	 *    $component->OnClick->insertAt(0,array($object,'buttonClicked'));
1264 2
	 *    $component->OnClick[] = function ($sender, $param) { ... };
1265 1
	 * </code>
1266 1
	 * which are equivalent to the following
1267
	 * <code>
1268
	 *    $component->getEventHandlers('OnClick')->add(array($object,'buttonClicked'));
1269
	 *    $component->getEventHandlers('OnClick')->insertAt(0,array($object,'buttonClicked'));
1270
	 * </code>
1271
	 *
1272
	 * Due to the nature of {@link getEventHandlers}, any active behaviors defining
1273
	 * new 'on' events, this method will pass through to the behavior transparently.
1274
	 *
1275
	 * @param string $name the event name
1276
	 * @param callable $handler the event handler
1277
	 * @param null|numeric $priority the priority of the handler, defaults to null which translates into the
1278
	 * default priority of 10.0 within {@link TWeakCallableCollection}
1279
	 * @throws TInvalidOperationException if the event does not exist
1280
	 */
1281
	public function attachEventHandler($name, $handler, $priority = null)
1282
	{
1283
		$this->getEventHandlers($name)->add($handler, $priority);
1284
	}
1285
1286
	/**
1287
	 * Detaches an existing event handler.
1288
	 * This method is the opposite of {@link attachEventHandler}.  It will detach
1289 1
	 * any 'on' events defined by an objects active behaviors as well.
1290
	 * @param string $name event name
1291 1
	 * @param callable $handler the event handler to be removed
1292 1
	 * @param null|false|numeric $priority the priority of the handler, defaults to false which translates
1293 1
	 * to an item of any priority within {@link TWeakCallableCollection}; null means the default priority
1294
	 * @return bool if the removal is successful
1295
	 */
1296
	public function detachEventHandler($name, $handler, $priority = false)
1297
	{
1298
		if ($this->hasEventHandler($name)) {
1299
			try {
1300
				$this->getEventHandlers($name)->remove($handler, $priority);
0 ignored issues
show
Bug introduced by
It seems like $priority can also be of type Prado\numeric; however, parameter $priority of Prado\Collections\TWeakC...bleCollection::remove() does only seem to accept boolean|double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1300
				$this->getEventHandlers($name)->remove($handler, /** @scrutinizer ignore-type */ $priority);
Loading history...
1301
				return true;
1302
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1303
			}
1304
		}
1305
		return false;
1306
	}
1307
1308
	/**
1309
	 * Raises an event.  This raises both inter-object 'on' events and global 'fx' events.
1310
	 * This method represents the happening of an event and will
1311
	 * invoke all attached event handlers for the event in {@link TWeakCallableCollection} order.
1312 1
	 * This method does not handle intra-object/behavior dynamic 'dy' events.
1313
	 *
1314 1
	 * There are ways to handle event responses.  By default {@link EVENT_RESULT_FILTER},
1315 1
	 * all event responses are stored in an array, filtered for null responses, and returned.
1316
	 * If {@link EVENT_RESULT_ALL} is specified, all returned results will be stored along
1317
	 * with the sender and param in an array
1318
	 * <code>
1319
	 * 		$result[] = array('sender'=>$sender,'param'=>$param,'response'=>$response);
1320
	 * </code>
1321
	 *
1322
	 * If {@link EVENT_RESULT_FEED_FORWARD} is specified, then each handler result is then
1323
	 * fed forward as the parameters for the next event.  This allows for events to filter data
1324
	 * directly by affecting the event parameters
1325
	 *
1326 6
	 * If a callable function is set in the response type or the post function filter is specified then the
1327
	 * result of each called event handler is post processed by the callable function.  Used in
1328 6
	 * combination with {@link EVENT_RESULT_FEED_FORWARD}, any event (and its result) can be chained.
1329 6
	 *
1330
	 * When raising a global 'fx' event, registered handlers in the global event list for
1331 1
	 * {@link GLOBAL_RAISE_EVENT_LISTENER} are always added into the set of event handlers.  In this way,
1332
	 * these global events are always raised for every global 'fx' event.  The registered handlers for global
1333
	 * raiseEvent events have priorities.  Any registered global raiseEvent event handlers with a priority less than zero
1334
	 * are added before the main event handlers being raised and any registered global raiseEvent event handlers
1335
	 * with a priority equal or greater than zero are added after the main event handlers being raised.  In this way
1336
	 * all {@link GLOBAL_RAISE_EVENT_LISTENER} handlers are always called for every raised 'fx' event.
1337
	 *
1338
	 * Behaviors may implement the following functions with TBehaviors:
1339
	 * <code>
1340
	 *	public function dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction[, TCallChain $chain) {
1341
	 *      ....  //  Your logic
1342 6
	 *  	return $chain->dyPreRaiseEvent($name, $sender, $param, $responsetype, $postfunction); //eg, the event name may be filtered/changed
1343
	 *  }
1344 6
	 *	public function dyIntraRaiseEventTestHandler($handler, $sender, $param, $name, TCallChain $chain) {
1345 6
	 *      ....  //  Your logic
1346
	 *  	return $chain->dyIntraRaiseEventTestHandler($handler, $sender, $param, $name); //should this particular handler be executed?  true/false
1347 1
	 *  }
1348
	 *  public function dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response, TCallChain $chain) {
1349
	 *      ....  //  Your logic
1350
	 *		return $chain->dyIntraRaiseEventPostHandler($name, $sender, $param, $handler, $response); //contains the per handler response
1351
	 *  }
1352
	 *  public function dyPostRaiseEvent($responses, $name, $sender, $param,$ responsetype, $postfunction, TCallChain $chain) {
1353
	 *      ....  //  Your logic
1354
	 *		return $chain->dyPostRaiseEvent($responses, $name, $sender, $param,$ responsetype, $postfunction);
1355
	 *  }
1356
	 * </code>
1357
	 * to be executed when raiseEvent is called.  The 'intra' dynamic events are called per handler in
1358
	 * the handler loop.  TClassBehaviors prepend the object being raised.
1359
	 *
1360
	 * dyPreRaiseEvent has the effect of being able to change the event being raised.  This intra
1361
	 * object/behavior event returns the name of the desired event to be raised.  It will pass through
1362
	 * if no dynamic event is specified, or if the original event name is returned.
1363
	 * dyIntraRaiseEventTestHandler returns true or false as to whether a specific handler should be
1364
	 * called for a specific raised event (and associated event arguments)
1365
	 * dyIntraRaiseEventPostHandler does not return anything.  This allows behaviors to access the results
1366
	 * of an event handler in the per handler loop.
1367
	 * dyPostRaiseEvent returns the responses.  This allows for any post processing of the event
1368 6
	 * results from the sum of all event handlers
1369
	 *
1370 6
	 * When handling a catch-all {@link __dycall}, the method name is the name of the event
1371 5
	 * and the parameters are the sender, the param, and then the name of the event.
1372
	 *
1373 6
	 * In the rare circumstance that the event handlers need to be raised in reverse order, then
1374
	 * specifying {@see TEventResults::EVENT_REVERSE} can be used to reverse the order of the
1375
	 * handlers.
1376
	 *
1377 6
	 * @param string $name the event name
1378
	 * @param mixed $sender the event sender object
1379
	 * @param \Prado\TEventParameter $param the event parameter
1380 6
	 * @param null|numeric $responsetype how the results of the event are tabulated.  default: {@link EVENT_RESULT_FILTER}  The default filters out
1381 6
	 *		null responses. optional
1382 1
	 * @param null|callable $postfunction any per handler filtering of the response result needed is passed through
1383
	 *		this if not null. default: null.  optional
1384 6
	 * @throws TInvalidOperationException if the event is undefined
1385 6
	 * @throws TInvalidDataValueException If an event handler is invalid
1386
	 * @return mixed the results of the event
1387 6
	 */
1388 1
	public function raiseEvent($name, $sender, $param, $responsetype = null, $postfunction = null)
1389
	{
1390 6
		$p = $param;
0 ignored issues
show
Unused Code introduced by
The assignment to $p is dead and can be removed.
Loading history...
1391 6
		if (is_callable($responsetype)) {
1392 6
			$postfunction = $responsetype;
1393 6
			$responsetype = null;
1394
		}
1395
1396
		if ($responsetype === null) {
1397
			$responsetype = TEventResults::EVENT_RESULT_FILTER;
1398
		}
1399
1400
		$name = strtolower($name);
1401
		$responses = [];
1402
1403
		if($param instanceof IEventParameter) {
0 ignored issues
show
introduced by
$param is always a sub-type of Prado\IEventParameter.
Loading history...
1404
			$param->setEventName($name);
1405
		}
1406
1407
		$this->callBehaviorsMethod('dyPreRaiseEvent', $name, $name, $sender, $param, $responsetype, $postfunction);
1408
1409 6
		if ($this->hasEventHandler($name) || $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1410
			$handlers = $this->getEventHandlers($name);
1411 6
			$handlerArray = $handlers->toArray();
1412 5
			if (strncasecmp($name, 'fx', 2) === 0 && $this->hasEventHandler(TComponent::GLOBAL_RAISE_EVENT_LISTENER)) {
1413
				$globalhandlers = $this->getEventHandlers(TComponent::GLOBAL_RAISE_EVENT_LISTENER);
1414 6
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(0));
0 ignored issues
show
Bug introduced by
0 of type integer is incompatible with the type Prado\Collections\numeric expected by parameter $priority of Prado\Collections\TWeakC...:toArrayBelowPriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1414
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(/** @scrutinizer ignore-type */ 0), $handlerArray, $globalhandlers->toArrayAbovePriority(0));
Loading history...
Bug introduced by
0 of type integer is incompatible with the type Prado\Collections\numeric expected by parameter $priority of Prado\Collections\TWeakC...:toArrayAbovePriority(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1414
				$handlerArray = array_merge($globalhandlers->toArrayBelowPriority(0), $handlerArray, $globalhandlers->toArrayAbovePriority(/** @scrutinizer ignore-type */ 0));
Loading history...
1415
			}
1416
			$response = null;
1417
			if ($responsetype & TEventResults::EVENT_REVERSE) {
1418 6
				$handlerArray = array_reverse($handlerArray);
1419 6
			}
1420
			foreach ($handlerArray as $handler) {
1421
				$this->callBehaviorsMethod('dyIntraRaiseEventTestHandler', $return, $handler, $sender, $param, $name);
1422 6
				if ($return === false) {
1423
					continue;
1424
				}
1425 6
1426 6
				if (is_string($handler)) {
1427 6
					if (($pos = strrpos($handler, '.')) !== false) {
1428 6
						$object = $this->getSubProperty(substr($handler, 0, $pos));
1429 6
						$method = substr($handler, $pos + 1);
1430
						if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1431
							if ($method == '__dycall') {
1432
								$response = $object->__dycall($name, [$sender, $param]);
1433
							} else {
1434
								$response = $object->$method($sender, $param);
1435
							}
1436
						} else {
1437
							throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler);
1438
						}
1439 8
					} else {
1440
						$response = call_user_func($handler, $sender, $param);
1441 8
					}
1442
				} elseif (is_callable($handler, true)) {
1443
					if (is_object($handler) || is_string($handler[0])) {
1444
						$response = call_user_func($handler, $sender, $param);
1445
					} else {
1446
						[$object, $method] = $handler;
1447
						if (($pos = strrpos($method, '.')) !== false) {
1448
							$object = $object->getSubProperty(substr($method, 0, $pos));
1449
							$method = substr($method, $pos + 1);
1450
						}
1451
						if (Prado::method_visible($object, $method) || strncasecmp($method, 'dy', 2) === 0 || strncasecmp($method, 'fx', 2) === 0) {
1452
							if ($method == '__dycall') {
1453
								$response = $object->__dycall($name, [$sender, $param]);
1454
							} else {
1455
								$response = $object->$method($sender, $param);
1456
							}
1457
						} else {
1458
							throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, $handler[1]);
1459
						}
1460
					}
1461
				} else {
1462
					throw new TInvalidDataValueException('component_eventhandler_invalid', $this::class, $name, gettype($handler));
1463 6
				}
1464
1465 6
				$this->callBehaviorsMethod('dyIntraRaiseEventPostHandler', $return, $name, $sender, $param, $handler, $response);
1466 6
1467
				if ($postfunction) {
1468 6
					$response = call_user_func_array($postfunction, [$sender, $param, $this, $response]);
1469 6
				}
1470 6
1471 2
				if ($responsetype & TEventResults::EVENT_RESULT_ALL) {
1472
					$responses[] = ['sender' => $sender, 'param' => $param, 'response' => $response];
1473
				} else {
1474 6
					$responses[] = $response;
1475 6
				}
1476 1
1477
				if ($response !== null && ($responsetype & TEventResults::EVENT_RESULT_FEED_FORWARD)) {
1478 6
					$param = $response;
1479 6
				}
1480
			}
1481
		} elseif (strncasecmp($name, 'on', 2) === 0 && !$this->hasEvent($name)) {
1482
			throw new TInvalidOperationException('component_event_undefined', $this::class, $name);
1483 6
		}
1484
1485
		if ($responsetype & TEventResults::EVENT_RESULT_FILTER) {
1486
			$responses = array_filter($responses);
1487
		}
1488
1489
		$this->callBehaviorsMethod('dyPostRaiseEvent', $responses, $responses, $name, $sender, $param, $responsetype, $postfunction);
1490
1491
		return $responses;
1492
	}
1493
1494 37
	/**
1495
	 * Evaluates a PHP expression in the context of this control.
1496 37
	 *
1497 6
	 * Behaviors may implement the function:
1498 4
	 * <code>
1499
	 *	public function dyEvaluateExpressionFilter($expression, TCallChain $chain) {
1500 6
	 * 		return $chain->dyEvaluateExpressionFilter(str_replace('foo', 'bar', $expression)); //example
1501
	 * }
1502
	 * </code>
1503 37
	 * to be executed when evaluateExpression is called.  All attached behaviors are notified through
1504
	 * dyEvaluateExpressionFilter.  The chaining is important in this function due to the filtering
1505
	 * pass-through effect.
1506
	 *
1507
	 * @param string $expression PHP expression
1508
	 * @throws TInvalidOperationException if the expression is invalid
1509
	 * @return mixed the expression result
1510
	 */
1511
	public function evaluateExpression($expression)
1512
	{
1513 1
		$this->callBehaviorsMethod('dyEvaluateExpressionFilter', $expression, $expression);
1514
		try {
1515 1
			return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1516 1
		} catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1517 1
			throw new TInvalidOperationException('component_expression_invalid', $this::class, $expression, $e->getMessage());
1518
		}
1519
	}
1520 1
1521
	/**
1522
	 * Evaluates a list of PHP statements.
1523
	 *
1524 1
	 * Behaviors may implement the function:
1525
	 * <code>
1526
	 *	public function dyEvaluateStatementsFilter($statements, TCallChain $chain) {
1527
	 * 		return $chain->dyEvaluateStatementsFilter(str_replace('foo', 'bar', $statements)); //example
1528
	 * }
1529
	 * </code>
1530 1
	 * to be executed when evaluateStatements is called.  All attached behaviors are notified through
1531
	 * dyEvaluateStatementsFilter.  The chaining is important in this function due to the filtering
1532 1
	 * pass-through effect.
1533 1
	 *
1534 1
	 * @param string $statements PHP statements
1535
	 * @throws TInvalidOperationException if the statements are invalid
1536 1
	 * @return string content echoed or printed by the PHP statements
1537
	 */
1538 1
	public function evaluateStatements($statements)
1539
	{
1540
		$this->callBehaviorsMethod('dyEvaluateStatementsFilter', $statements, $statements);
1541
		try {
1542
			ob_start();
1543
			if (eval($statements) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1544
				throw new \Exception('');
1545
			}
1546
			$content = ob_get_contents();
1547
			ob_end_clean();
1548
			return $content;
1549
		} catch (\Exception $e) {
1550
			throw new TInvalidOperationException('component_statements_invalid', $this::class, $statements, $e->getMessage());
1551
		}
1552
	}
1553
1554
	/**
1555
	 * This method is invoked after the component is instantiated by a template.
1556
	 * When this method is invoked, the component's properties have been initialized.
1557
	 * The default implementation of this method will invoke
1558
	 * the potential parent component's {@link addParsedObject}.
1559
	 * This method can be overridden.
1560
	 *
1561 27
	 * Behaviors may implement the function:
1562
	 * <code>
1563 27
	 *	public function dyCreatedOnTemplate($parent, TCallChain $chain) {
1564 3
	 * 		return $chain->dyCreatedOnTemplate($parent); //example
1565
	 *  }
1566 27
	 * </code>
1567 1
	 * to be executed when createdOnTemplate is called.  All attached behaviors are notified through
1568
	 * dyCreatedOnTemplate.
1569 27
	 *
1570 26
	 * @param \Prado\TComponent $parent potential parent of this control
1571
	 * @see addParsedObject
1572 27
	 */
1573 27
	public function createdOnTemplate($parent)
1574
	{
1575 27
		$this->callBehaviorsMethod('dyCreatedOnTemplate', $parent, $parent);
1576 27
		$parent->addParsedObject($this);
1577 27
	}
1578 27
1579
	/**
1580
	 * Processes an object that is created during parsing template.
1581
	 * The object can be either a component or a static text string.
1582
	 * This method can be overridden to customize the handling of newly created objects in template.
1583
	 * Only framework developers and control developers should use this method.
1584
	 *
1585
	 * Behaviors may implement the function:
1586
	 * <code>
1587
	 *	public function dyAddParsedObject($object, TCallChain $chain) {
1588
	 *      return $chain-> dyAddParsedObject($object);
1589
	 *  }
1590
	 * </code>
1591
	 * to be executed when addParsedObject is called.  All attached behaviors are notified through
1592
	 * dyAddParsedObject.
1593
	 *
1594
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1595
	 * @see createdOnTemplate
1596
	 */
1597
	public function addParsedObject($object)
1598 18
	{
1599
		$this->callBehaviorsMethod('dyAddParsedObject', $return, $object);
1600 18
	}
1601 18
1602 18
	/**
1603 18
	 *This is the method registered for all instanced objects should a class behavior be added after
1604 18
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1605 18
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior added to this instance.
1606
	 * @param mixed $sender the application
1607
	 * @param TClassBehaviorEventParameter $param
1608
	 * @since 3.2.3
1609
	 */
1610
	public function fxAttachClassBehavior($sender, $param)
1611
	{
1612
		if ($this->isa($param->getClass())) {
1613
			if (($behavior = $param->getBehavior()) instanceof IBehavior) {
1614
				$behavior = clone $behavior;
1615
			}
1616
			return $this->attachBehavior($param->getName(), $behavior, $param->getPriority());
1617
		}
1618
	}
1619
1620
	/**
1621 8
	 *	This is the method registered for all instanced objects should a class behavior be removed after
1622
	 * the class is instanced.  Only when the class to which the behavior is being added is in this
1623 8
	 * object's class hierarchy, via {@link getClassHierarchy}, is the behavior removed from this instance.
1624 8
	 * @param mixed $sender the application
1625 8
	 * @param TClassBehaviorEventParameter $param
1626
	 * @since 3.2.3
1627 8
	 */
1628
	public function fxDetachClassBehavior($sender, $param)
1629
	{
1630
		if ($this->isa($param->getClass())) {
1631
			return $this->detachBehavior($param->getName(), $param->getPriority());
1632
		}
1633
	}
1634
1635
	/**
1636
	 * instanceBehavior is an internal method that takes a Behavior Object, a class name, or array of
1637
	 * ['class' => 'MyBehavior', 'property1' => 'Value1'...] and creates a Behavior in return. eg.
1638
	 * <code>
1639
	 *		$b = $this->instanceBehavior('MyBehavior');
1640
	 * 		$b = $this->instanceBehavior(['class' => 'MyBehavior', 'property1' => 'Value1']);
1641 8
	 * 		$b = $this->instanceBehavior(new MyBehavior);
1642
	 * </code>
1643 8
	 * If the behavior is an array, the key IBaseBehavior::CONFIG_KEY is stripped and used to initialize
1644 8
	 * the behavior.
1645 8
	 *
1646
	 * @param array|IBaseBehavior|string $behavior string, Behavior, or array of ['class' => 'MyBehavior', 'property1' => 'Value1' ...].
1647 8
	 * @throws TInvalidDataTypeException if the behavior is not an {@link IBaseBehavior}
1648
	 * @return IBaseBehavior&TComponent an instance of $behavior or $behavior itself
1649
	 * @since 4.2.0
1650
	 */
1651
	protected static function instanceBehavior($behavior)
1652
	{
1653
		$config = null;
1654
		$isArray = false;
1655
		$init = false;
1656
		if (is_string($behavior) || (($isArray = is_array($behavior)) && isset($behavior['class']))) {
1657
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, $behavior)) {
0 ignored issues
show
Bug introduced by
It seems like $behavior can also be of type string; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1657
			if ($isArray && array_key_exists(IBaseBehavior::CONFIG_KEY, /** @scrutinizer ignore-type */ $behavior)) {
Loading history...
1658
				$config = $behavior[IBaseBehavior::CONFIG_KEY];
1659
				unset($behavior[IBaseBehavior::CONFIG_KEY]);
1660
			}
1661
			$behavior = Prado::createComponent($behavior);
1662
			$init = true;
1663
		}
1664
		if (!($behavior instanceof IBaseBehavior)) {
1665
			throw new TInvalidDataTypeException('component_not_a_behavior', $behavior::class);
1666
		}
1667
		if ($init) {
1668
			$behavior->init($config);
1669
		}
1670
		return $behavior;
1671
	}
1672
1673
1674
	/**
1675
	 *	This will add a class behavior to all classes instanced (that are listening) and future newly instanced objects.
1676 16
	 * This registers the behavior for future instances and pushes the changes to all the instances that are listening as well.
1677
	 * The universal class behaviors are stored in an inverted stack with the latest class behavior being at the first position in the array.
1678 16
	 * This is done so class behaviors are added last first.
1679 16
	 * @param string $name name the key of the class behavior
1680 16
	 * @param object|string $behavior class behavior or name of the object behavior per instance
1681 16
	 * @param null|array|IBaseBehavior|string $class string of class or class on which to attach this behavior.  Defaults to null which will error
1682 16
	 *	but more important, if this is on PHP 5.3 it will use Late Static Binding to derive the class
1683
	 * it should extend.
1684 1
	 * <code>
1685
	 *   TPanel::attachClassBehavior('javascripts', new TJsPanelClassBehavior());
1686 2
	 *   TApplication::attachClassBehavior('jpegize', \Prado\Util\Behaviors\TJPEGizeAssetBehavior::class, \Prado\Web\TFileAsset::class);
1687
	 * </code>
1688
	 * An array is used to initialize values of the behavior. eg. ['class' => '\\MyBehavior', 'property' => 'value'].
1689
	 * @param null|numeric $priority priority of behavior, default: null the default
1690
	 *  priority of the {@link TWeakCallableCollection}  Optional.
1691
	 * @throws TInvalidOperationException if the class behavior is being added to a
1692
	 *  {@link TComponent}; due to recursion.
1693
	 * @throws TInvalidOperationException if the class behavior is already defined
1694
	 * @return array|object the behavior if its an IClassBehavior and an array of all
1695
	 * behaviors that have been attached from 'fxAttachClassBehavior' when the Class
1696
	 * Behavior being attached is a per instance IBaseBehavior.
1697
	 * @since 3.2.3
1698
	 */
1699
	public static function attachClassBehavior($name, $behavior, $class = null, $priority = null)
1700
	{
1701
		if (!$class) {
1702
			$class = get_called_class();
1703
		}
1704 16
		if (!$class) {
1705
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1706 16
		}
1707 16
1708 16
		$class = strtolower($class);
1709 16
		if ($class === strtolower(TComponent::class)) {
1710 16
			throw new TInvalidOperationException('component_no_tcomponent_class_behaviors');
1711
		}
1712 1
		if (empty(self::$_um[$class])) {
1713
			self::$_um[$class] = [];
1714 2
		}
1715
		$name = strtolower($name !== null ? $name : '');
0 ignored issues
show
introduced by
The condition $name !== null is always true.
Loading history...
1716
		if (!empty($name) && !is_numeric($name) && isset(self::$_um[$class][$name])) {
1717
			throw new TInvalidOperationException('component_class_behavior_defined', $class, $name);
1718
		}
1719
		$behaviorObject = self::instanceBehavior($behavior);
1720
		$behaviorObject->setName($name);
1721
		$isClassBehavior = $behaviorObject instanceof \Prado\Util\IClassBehavior;
1722 5
		$param = new TClassBehaviorEventParameter($class, $name, $isClassBehavior ? $behaviorObject : $behavior, $priority);
0 ignored issues
show
Bug introduced by
It seems like $isClassBehavior ? $behaviorObject : $behavior can also be of type string; however, parameter $behavior of Prado\Util\TClassBehavio...arameter::__construct() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1722
		$param = new TClassBehaviorEventParameter($class, $name, /** @scrutinizer ignore-type */ $isClassBehavior ? $behaviorObject : $behavior, $priority);
Loading history...
1723
		if (empty($name) || is_numeric($name)) {
1724 5
			self::$_um[$class][] = $param;
1725 5
		} else {
1726 5
			self::$_um[$class] = [$name => $param] + self::$_um[$class];
1727 5
		}
1728 5
		$results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
0 ignored issues
show
Bug introduced by
The method raiseEvent() does not exist on Prado\Util\IBaseBehavior. It seems like you code against a sub-type of said class. However, the method does not exist in Prado\Util\IBehavior or Prado\Util\IClassBehavior. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1728
		/** @scrutinizer ignore-call */ 
1729
  $results = $behaviorObject->raiseEvent('fxAttachClassBehavior', null, $param);
Loading history...
1729
		return $isClassBehavior ? $behaviorObject : $results;
1730
	}
1731
1732
	/**
1733
	 *	This will remove a behavior from a class.  It unregisters it from future instances and
1734
	 * pulls the changes from all the instances that are listening as well.
1735
	 * PHP 5.3 uses Late Static Binding to derive the static class upon which this method is called.
1736
	 * @param string $name the key of the class behavior
1737
	 * @param string $class class on which to attach this behavior.  Defaults to null.
1738 5
	 * @param null|false|numeric $priority priority: false is any priority, null is default
1739
	 *		{@link TWeakCallableCollection} priority, and numeric is a specific priority.
1740 5
	 * @throws TInvalidOperationException if the the class cannot be derived from Late Static Binding and is not
1741 5
	 * not supplied as a parameter.
1742
	 * @return null|array|object the behavior if its an IClassBehavior and an array of all behaviors
1743 5
	 * that have been detached from 'fxDetachClassBehavior' when the Class Behavior being
1744 5
	 * attached is a per instance IBehavior.  Null if no behavior of $name to detach.
1745
	 * @since 3.2.3
1746 5
	 */
1747 5
	public static function detachClassBehavior($name, $class = null, $priority = false)
1748
	{
1749 5
		if (!$class) {
1750 5
			$class = get_called_class();
1751
		}
1752 5
		if (!$class) {
1753
			throw new TInvalidOperationException('component_no_class_provided_nor_late_binding');
1754
		}
1755
1756
		$class = strtolower($class);
1757
		$name = strtolower($name);
1758
		if (empty(self::$_um[$class]) || !isset(self::$_um[$class][$name])) {
1759
			return null;
1760
		}
1761
		$param = self::$_um[$class][$name];
1762
		$behavior = $param->getBehavior();
1763
		$behaviorObject = self::instanceBehavior($behavior);
1764
		$behaviorObject->setName($name);
1765
		$isClassBehavior = $behaviorObject instanceof IClassBehavior;
1766
		unset(self::$_um[$class][$name]);
1767
		if(empty(self::$_um[$class])) {
1768
			unset(self::$_um[$class]);
1769
		}
1770
		$results = $behaviorObject->raiseEvent('fxDetachClassBehavior', null, $param);
1771
		return $isClassBehavior ? $behaviorObject : $results;
1772
	}
1773
1774
	/**
1775
	 * Returns the named behavior object.  If the $behaviorname is not found, but is
1776
	 * an existing class or interface, this will return the first instanceof.
1777
	 * The name 'asa' stands for 'as a'.
1778
	 * @param string $behaviorname the behavior name or the class name of the behavior.
1779
	 * @return object the behavior object of name or class, or null if the behavior does not exist
1780
	 * @since 3.2.3
1781
	 */
1782
	public function asa($behaviorname)
1783
	{
1784
		$behaviorname = strtolower($behaviorname);
1785
		if (isset($this->_m[$behaviorname])) {
1786
			return $this->_m[$behaviorname];
1787
		}
1788
		if ((class_exists($behaviorname, false) || interface_exists($behaviorname, false)) && $this->_m) {
1789
			foreach($this->_m->toArray() as $behavior) {
1790
				if ($behavior instanceof $behaviorname) {
1791
					return $behavior;
1792
				}
1793
			}
1794
		}
1795
		return null;
1796
	}
1797
1798
	/**
1799
	 * Returns whether or not the object or any of the behaviors are of a particular class.
1800
	 * The name 'isa' stands for 'is a'.  This first checks if $this is an instanceof the class.
1801
	 * Then it checks if the $class is in the hierarchy, which includes first level traits.
1802
	 * It then checks each Behavior.  If a behavior implements {@link IInstanceCheck},
1803
	 * then the behavior can determine what it is an instanceof.  If this behavior function returns true,
1804
	 * then this method returns true.  If the behavior instance checking function returns false,
1805
	 * then no further checking is performed as it is assumed to be correct.
1806
	 *
1807
	 * If the behavior instance check function returns nothing or null or the behavior
1808
	 * doesn't implement the {@link IInstanceCheck} interface, then the default instanceof occurs.
1809
	 * The default isa behavior is to check if the behavior is an instanceof the class.
1810
	 *
1811
	 * The behavior {@link IInstanceCheck} is to allow a behavior to have the host object
1812
	 * act as a completely different object.
1813
	 *
1814
	 * @param mixed|string $class class or string
1815
	 * @return bool whether or not the object or a behavior is an instance of a particular class
1816
	 * @since 3.2.3
1817
	 */
1818
	public function isa($class)
1819
	{
1820
		if ($this instanceof $class || in_array(strtolower(is_object($class) ? $class::class : $class), $this->getClassHierarchy(true))) {
1821
			return true;
1822
		}
1823
		if ($this->_m !== null && $this->getBehaviorsEnabled()) {
1824
			foreach ($this->_m->toArray() as $behavior) {
1825
				if (!$behavior->getEnabled()) {
1826
					continue;
1827
				}
1828
1829
				$check = null;
1830
				if (($behavior->isa(\Prado\Util\IInstanceCheck::class)) && $check = $behavior->isinstanceof($class, $this)) {
1831
					return true;
1832
				}
1833
				if ($check === null && ($behavior->isa($class))) {
1834
					return true;
1835
				}
1836
			}
1837
		}
1838
		return false;
1839
	}
1840
1841
	/**
1842
	 * Returns all the behaviors attached to the TComponent.  IBaseBehavior[s] may
1843
	 * be attached but not {@link IBaseBehavior::getEnabled Enabled}.
1844
	 * @param ?string $class Filters the result by class, default null for no filter.
1845
	 * @return array The behaviors [optionally filtered] attached to the TComponent.
1846
	 * @since 4.2.2
1847
	 */
1848
	public function getBehaviors(?string $class = null)
1849
	{
1850
		if ($class === null) {
1851
			return isset($this->_m) ? $this->_m->toArray() : [];
1852
		} elseif (class_exists($class, false) || interface_exists($class, false)) {
1853
			return array_filter($this->_m->toArray(), fn ($b) => $b instanceof $class);
1854
		}
1855
		return [];
1856
	}
1857
1858
	/**
1859
	 * Attaches a list of behaviors to the component.
1860
	 * Each behavior is indexed by its name and should be an instance of
1861
	 * {@link IBaseBehavior}, a string specifying the behavior class, or a
1862
	 * {@link TClassBehaviorEventParameter}.
1863
	 * @param array $behaviors list of behaviors to be attached to the component
1864
	 * @param bool $cloneIBehavior Should IBehavior be cloned before attaching.
1865
	 *   Default is false.
1866
	 * @since 3.2.3
1867
	 */
1868
	public function attachBehaviors($behaviors, bool $cloneIBehavior = false)
1869
	{
1870
		foreach ($behaviors as $name => $behavior) {
1871
			if ($behavior instanceof TClassBehaviorEventParameter) {
1872
				$paramBehavior = $behavior->getBehavior();
1873
				if ($cloneIBehavior && ($paramBehavior instanceof IBehavior)) {
1874
					$paramBehavior = clone $paramBehavior;
1875
				}
1876
				$this->attachBehavior($behavior->getName(), $paramBehavior, $behavior->getPriority());
1877
			} else {
1878
				if ($cloneIBehavior && ($behavior instanceof IBehavior)) {
1879
					$behavior = clone $behavior;
1880
				}
1881
				$this->attachBehavior($name, $behavior);
1882
			}
1883
		}
1884
	}
1885
1886
	/**
1887
	 * Detaches select behaviors from the component.
1888
	 * Each behavior is indexed by its name and should be an instance of
1889
	 * {@link IBaseBehavior}, a string specifying the behavior class, or a
1890
	 * {@link TClassBehaviorEventParameter}.
1891
	 * @param array $behaviors list of behaviors to be detached from the component
1892
	 * @since 3.2.3
1893
	 */
1894
	public function detachBehaviors($behaviors)
1895
	{
1896
		if ($this->_m !== null) {
1897
			foreach ($behaviors as $name => $behavior) {
1898
				if ($behavior instanceof TClassBehaviorEventParameter) {
1899
					$this->detachBehavior($behavior->getName(), $behavior->getPriority());
1900
				} else {
1901
					$this->detachBehavior(is_string($behavior) ? $behavior : $name);
1902
				}
1903
			}
1904
		}
1905
	}
1906
1907
	/**
1908
	 * Detaches all behaviors from the component.
1909
	 * @since 3.2.3
1910
	 */
1911
	public function clearBehaviors()
1912
	{
1913
		if ($this->_m !== null) {
1914
			foreach ($this->_m->getKeys() as $name) {
1915
				$this->detachBehavior($name);
1916
			}
1917
			$this->_m = null;
1918
		}
1919
	}
1920
1921
	/**
1922
	 * Attaches a behavior to this component.
1923
	 * This method will create the behavior object based on the given
1924
	 * configuration. After that, the behavior object will be initialized
1925
	 * by calling its {@link IBaseBehavior::attach} method.
1926
	 *
1927
	 * Already attached behaviors may implement the function:
1928
	 * <code>
1929
	 *	public function dyAttachBehavior($name,$behavior[, ?TCallChain $chain = null]) {
1930
	 *      if ($chain)
1931
	 *          return $chain->dyDetachBehavior($name, $behavior);
1932
	 *  }
1933
	 * </code>
1934
	 * to be executed when attachBehavior is called.  All attached behaviors are notified through
1935
	 * dyAttachBehavior.
1936
	 *
1937
	 * @param null|numeric|string $name the behavior's name. It should uniquely identify this behavior.
1938
	 * @param array|IBaseBehavior|string $behavior the behavior configuration. This is the name of the Behavior Class
1939
	 * instanced by {@link PradoBase::createComponent}, or is a Behavior, or is an array of
1940
	 * ['class'=>'TBehavior' property1='value 1' property2='value2'...] with the class and properties
1941
	 * with values.
1942
	 * @param null|numeric $priority
1943
	 * @return IBaseBehavior the behavior object
1944
	 * @since 3.2.3
1945
	 */
1946
	public function attachBehavior($name, $behavior, $priority = null)
1947
	{
1948
		$name = strtolower($name !== null ? $name : '');
1949
		if ($this->_m && isset($this->_m[$name])) {
1950
			$this->detachBehavior($name);
1951
		}
1952
		$behavior = self::instanceBehavior($behavior);
1953
		if ($this->_m === null) {
1954
			$this->_m = new TPriorityMap();
1955
		}
1956
		if (empty($name) || is_numeric($name)) {
1957
			$name = $this->_m->getNextIntegerKey();
1958
		}
1959
		$this->_m->add($name, $behavior, $priority);
1960
		$behavior->setName($name);
1961
		$behavior->attach($this);
1962
		$this->callBehaviorsMethod('dyAttachBehavior', $return, $name, $behavior);
1963
		return $behavior;
1964
	}
1965
1966
	/**
1967
	 * Detaches a behavior from the component.
1968
	 * The behavior's {@link IBaseBehavior::detach} method will be invoked.
1969
	 *
1970
	 * Behaviors may implement the function:
1971
	 * <code>
1972
	 *	public function dyDetachBehavior($name, $behavior[, ?TCallChain $chain = null]) {
1973
	 *      if ($chain)
1974
	 *          return $chain->dyDetachBehavior($name, $behavior);
1975
	 *  }
1976
	 * </code>
1977
	 * to be executed when detachBehavior is called.  All attached behaviors are notified through
1978
	 * dyDetachBehavior.
1979
	 *
1980
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
1981
	 * @param false|numeric $priority the behavior's priority. This defaults to false, which is any priority.
1982
	 * @return null|IBaseBehavior the detached behavior. Null if the behavior does not exist.
1983
	 * @since 3.2.3
1984
	 */
1985
	public function detachBehavior($name, $priority = false)
1986
	{
1987
		$name = strtolower($name);
1988
		if ($this->_m != null && ($behavior = $this->_m->itemAt($name, $priority))) {
1989
			$this->callBehaviorsMethod('dyDetachBehavior', $return, $name, $behavior);
1990
			$behavior->detach($this);
1991
			$this->_m->remove($name, $priority);
1992
			return $behavior;
1993
		}
1994
		return null;
1995
	}
1996
1997
	/**
1998
	 * Enables all behaviors attached to this component independent of the behaviors
1999
	 *
2000
	 * Behaviors may implement the function:
2001
	 * <code>
2002
	 *	public function dyEnableBehaviors([?TCallChain $chain = null]) {
2003
	 *      if ($chain)
2004
	 *          return $chain->dyEnableBehaviors();
2005
	 *  }
2006
	 * </code>
2007
	 * to be executed when enableBehaviors is called.  All attached behaviors are notified through
2008
	 * dyEnableBehaviors.
2009
	 * @since 3.2.3
2010
	 */
2011
	public function enableBehaviors()
2012
	{
2013
		if (!$this->_behaviorsenabled) {
2014
			$this->_behaviorsenabled = true;
2015
			$this->callBehaviorsMethod('dyEnableBehaviors', $return);
2016
		}
2017
	}
2018
2019
	/**
2020
	 * Disables all behaviors attached to this component independent of the behaviors
2021
	 *
2022
	 * Behaviors may implement the function:
2023
	 * <code>
2024
	 *	public function dyDisableBehaviors([?TCallChain $chain = null]) {
2025
	 *      if ($chain)
2026
	 *          return $chain->dyDisableBehaviors();
2027
	 *  }
2028
	 * </code>
2029
	 * to be executed when disableBehaviors is called.  All attached behaviors are notified through
2030
	 * dyDisableBehaviors.
2031
	 * @since 3.2.3
2032
	 */
2033
	public function disableBehaviors()
2034
	{
2035
		if ($this->_behaviorsenabled) {
2036
			$callchain = $this->getCallChain('dyDisableBehaviors');
2037
			$this->_behaviorsenabled = false;
2038
			if ($callchain) { // normal dynamic events won't work because behaviors are disabled.
2039
				$callchain->call();
2040
			}
2041
		}
2042
	}
2043
2044
2045
	/**
2046
	 * Returns if all the behaviors are turned on or off for the object.
2047
	 * @return bool whether or not all behaviors are enabled (true) or not (false)
2048
	 * @since 3.2.3
2049
	 */
2050
	public function getBehaviorsEnabled()
2051
	{
2052
		return $this->_behaviorsenabled;
2053
	}
2054
2055
	/**
2056
	 * Enables an attached object behavior.  This cannot enable or disable whole class behaviors.
2057
	 * A behavior is only effective when it is enabled.
2058
	 * A behavior is enabled when first attached.
2059
	 *
2060
	 * Behaviors may implement the function:
2061
	 * <code>
2062
	 *	public function dyEnableBehavior($name, $behavior[, ?TCallChain $chain = null]) {
2063
	 *      if ($chain)
2064
	 *          return $chain->dyEnableBehavior($name, $behavior);
2065
	 *  }
2066
	 * </code>
2067
	 * to be executed when enableBehavior is called.  All attached behaviors are notified through
2068
	 * dyEnableBehavior.
2069
	 *
2070
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
2071
	 * @return bool Was the behavior found and enabled.
2072
	 * @since 3.2.3
2073
	 */
2074
	public function enableBehavior($name): bool
2075
	{
2076
		$name = strtolower($name);
2077
		if ($this->_m != null && isset($this->_m[$name])) {
2078
			$behavior = $this->_m[$name];
2079
			if ($behavior->getEnabled() === false) {
2080
				$behavior->setEnabled(true);
2081
				$this->callBehaviorsMethod('dyEnableBehavior', $return, $name, $behavior);
2082
			}
2083
			return true;
2084
		}
2085
		return false;
2086
	}
2087
2088
	/**
2089
	 * Disables an attached behavior.  This cannot enable or disable whole class behaviors.
2090
	 * A behavior is only effective when it is enabled.
2091
	 *
2092
	 * Behaviors may implement the function:
2093
	 * <code>
2094
	 *	public function dyDisableBehavior($name, $behavior[, ?TCallChain $chain = null]) {
2095
	 *      if ($chain)
2096
	 *          return $chain->dyDisableBehavior($name, $behavior);
2097
	 *  }
2098
	 * </code>
2099
	 * to be executed when disableBehavior is called.  All attached behaviors are notified through
2100
	 * dyDisableBehavior.
2101
	 *
2102
	 * @param string $name the behavior's name. It uniquely identifies the behavior.
2103
	 * @return bool Was the behavior found and disabled.
2104
	 * @since 3.2.3
2105
	 */
2106
	public function disableBehavior($name): bool
2107
	{
2108
		$name = strtolower($name);
2109
		if ($this->_m != null && isset($this->_m[$name])) {
2110
			$behavior = $this->_m[$name];
2111
			if ($behavior->getEnabled() === true) {
2112
				$behavior->setEnabled(false);
2113
				$this->callBehaviorsMethod('dyDisableBehavior', $return, $name, $behavior);
2114
			}
2115
			return true;
2116
		}
2117
		return false;
2118
	}
2119
2120
	/**
2121
	 * Returns an array with the names of all variables of that object that should be serialized.
2122
	 * Do not call this method. This is a PHP magic method that will be called automatically
2123
	 * prior to any serialization.
2124
	 */
2125
	public function __sleep()
2126
	{
2127
		$a = (array) $this;
2128
		$a = array_keys($a);
2129
		$exprops = [];
2130
		$this->_getZappableSleepProps($exprops);
2131
		return array_diff($a, $exprops);
2132
	}
2133
2134
	/**
2135
	 * Returns an array with the names of all variables of this object that should NOT be serialized
2136
	 * because their value is the default one or useless to be cached for the next page loads.
2137
	 * Reimplement in derived classes to add new variables, but remember to  also to call the parent
2138
	 * implementation first.
2139
	 * @param array $exprops by reference
2140
	 */
2141
	protected function _getZappableSleepProps(&$exprops)
2142
	{
2143
		if ($this->_listeningenabled === false) {
2144
			$exprops[] = "\0*\0_listeningenabled";
2145
		}
2146
		if ($this->_behaviorsenabled === true) {
2147
			$exprops[] = "\0*\0_behaviorsenabled";
2148
		}
2149
		$exprops[] = "\0*\0_e";
2150
		if ($this->_m === null) {
2151
			$exprops[] = "\0*\0_m";
2152
		}
2153
	}
2154
}
2155