Test Failed
Branch master (206474)
by Fabio
18:24
created

TTemplate::parse()   F

Complexity

Conditions 67
Paths 11736

Size

Total Lines 227
Code Lines 175

Duplication

Lines 56
Ratio 24.67 %

Importance

Changes 0
Metric Value
cc 67
eloc 175
nc 11736
nop 1
dl 56
loc 227
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * TTemplateManager and TTemplate class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Web\UI
10
 */
11
12
namespace Prado\Web\UI;
13
14
use Prado\Prado;
15
use Prado\TComponent;
16
use Prado\Web\Javascripts\TJavaScriptLiteral;
17
use Prado\Exceptions\TConfigurationException;
18
use Prado\Exceptions\TTemplateException;
19
20
/**
21
 * TTemplate implements PRADO template parsing logic.
22
 * A TTemplate object represents a parsed PRADO control template.
23
 * It can instantiate the template as child controls of a specified control.
24
 * The template format is like HTML, with the following special tags introduced,
25
 * - component tags: a component tag represents the configuration of a component.
26
 * The tag name is in the format of com:ComponentType, where ComponentType is the component
27
 * class name. Component tags must be well-formed. Attributes of the component tag
28
 * are treated as either property initial values, event handler attachment, or regular
29
 * tag attributes.
30
 * - property tags: property tags are used to set large block of attribute values.
31
 * The property tag name is in the format of <prop:AttributeName> where AttributeName
32
 * can be a property name, an event name or a regular tag attribute name.
33
 * - group subproperty tags: subproperties of a common property can be configured using
34
 * <prop:MainProperty SubProperty1="Value1" SubProperty2="Value2" .../>
35
 * - directive: directive specifies the property values for the template owner.
36
 * It is in the format of <%@ property name-value pairs %>;
37
 * - expressions: They are in the format of <%= PHP expression %> and <%% PHP statements %>
38
 * - comments: There are two kinds of comments, regular HTML comments and special template comments.
39
 * The former is in the format of <!-- comments -->, which will be treated as text strings.
40
 * The latter is in the format of <!-- comments --!>, which will be stripped out.
41
 *
42
 * Tags other than the above are not required to be well-formed.
43
 *
44
 * A TTemplate object represents a parsed PRADO template. To instantiate the template
45
 * for a particular control, call {@link instantiateIn($control)}, which
46
 * will create and intialize all components specified in the template and
47
 * set their parent as $control.
48
 *
49
 * @author Qiang Xue <[email protected]>
50
 * @package Prado\Web\UI
51
 * @since 3.0
52
 */
53
class TTemplate extends \Prado\TApplicationComponent implements ITemplate
54
{
55
	/**
56
	 *  '<!--.*?--!>' - template comments
57
	 *  '<!--.*?-->'  - HTML comments
58
	 *	'<\/?com:([\w\.\\\]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>' - component tags
59
	 *	'<\/?prop:([\w\.\-]+)\s*>'  - property tags
60
	 *	'<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>'  - directives
61
	 *	'<%[%#~\/\\$=\\[](.*?)%>'  - expressions
62
	 *  '<prop:([\w\.\-]+)((?:\s*[\w\.\-]+=\'.*?\'|\s*[\w\.\-]+=".*?"|\s*[\w\.\-]+=<%.*?%>)*)\s*\/>' - group subproperty tags
63
	 */
64
	const REGEX_RULES = '/<!--.*?--!>|<!---.*?--->|<\/?com:([\w\.\\\]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.\-]+)\s*>|<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>|<%[%#~\/\\$=\\[](.*?)%>|<prop:([\w\.\-]+)((?:\s*[\w\.\-]+\s*=\s*\'.*?\'|\s*[\w\.\-]+\s*=\s*".*?"|\s*[\w\.\-]+\s*=\s*<%.*?%>)*)\s*\/>/msS';
65
66
	/**
67
	 * Different configurations of component property/event/attribute
68
	 */
69
	const CONFIG_DATABIND = 0;
70
	const CONFIG_EXPRESSION = 1;
71
	const CONFIG_ASSET = 2;
72
	const CONFIG_PARAMETER = 3;
73
	const CONFIG_LOCALIZATION = 4;
74
	const CONFIG_TEMPLATE = 5;
75
76
	/**
77
	 * @var array list of component tags and strings
78
	 */
79
	private $_tpl = [];
80
	/**
81
	 * @var array list of directive settings
82
	 */
83
	private $_directive = [];
84
	/**
85
	 * @var string context path
86
	 */
87
	private $_contextPath;
88
	/**
89
	 * @var string template file path (if available)
90
	 */
91
	private $_tplFile;
92
	/**
93
	 * @var int the line number that parsing starts from (internal use)
94
	 */
95
	private $_startingLine = 0;
96
	/**
97
	 * @var string template content to be parsed
98
	 */
99
	private $_content;
100
	/**
101
	 * @var bool whether this template is a source template
102
	 */
103
	private $_sourceTemplate = true;
104
	/**
105
	 * @var string hash code of the template
106
	 */
107
	private $_hashCode = '';
108
	private $_tplControl;
109
	private $_includedFiles = [];
110
	private $_includeAtLine = [];
111
	private $_includeLines = [];
112
113
114
	/**
115
	 * Constructor.
116
	 * The template will be parsed after construction.
117
	 * @param string $template the template string
118
	 * @param string $contextPath the template context directory
119
	 * @param null|string $tplFile the template file, null if no file
120
	 * @param int $startingLine the line number that parsing starts from (internal use)
121
	 * @param bool $sourceTemplate whether this template is a source template, i.e., this template is loaded from
122
	 * some external storage rather than from within another template.
123
	 */
124
	public function __construct($template, $contextPath, $tplFile = null, $startingLine = 0, $sourceTemplate = true)
125
	{
126
		$this->_sourceTemplate = $sourceTemplate;
127
		$this->_contextPath = $contextPath;
128
		$this->_tplFile = $tplFile;
129
		$this->_startingLine = $startingLine;
130
		$this->_content = $template;
131
		$this->_hashCode = md5($template);
132
		$this->parse($template);
133
		$this->_content = null; // reset to save memory
134
	}
135
136
	/**
137
	 * @return string  template file path if available, null otherwise.
138
	 */
139
	public function getTemplateFile()
140
	{
141
		return $this->_tplFile;
142
	}
143
144
	/**
145
	 * @return bool whether this template is a source template, i.e., this template is loaded from
146
	 * some external storage rather than from within another template.
147
	 */
148
	public function getIsSourceTemplate()
149
	{
150
		return $this->_sourceTemplate;
151
	}
152
153
	/**
154
	 * @return string context directory path
155
	 */
156
	public function getContextPath()
157
	{
158
		return $this->_contextPath;
159
	}
160
161
	/**
162
	 * @return array name-value pairs declared in the directive
163
	 */
164
	public function getDirective()
165
	{
166
		return $this->_directive;
167
	}
168
169
	/**
170
	 * @return string hash code that can be used to identify the template
171
	 */
172
	public function getHashCode()
173
	{
174
		return $this->_hashCode;
175
	}
176
177
	/**
178
	 * @return array the parsed template
179
	 */
180
	public function &getItems()
181
	{
182
		return $this->_tpl;
183
	}
184
185
	/**
186
	 * Instantiates the template.
187
	 * Content in the template will be instantiated as components and text strings
188
	 * and passed to the specified parent control.
189
	 * @param TControl $tplControl the control who owns the template
190
	 * @param null|TControl $parentControl the control who will become the root parent of the controls on the template. If null, it uses the template control.
191
	 */
192
	public function instantiateIn($tplControl, $parentControl = null)
193
	{
194
		$this->_tplControl = $tplControl;
195
		if ($parentControl === null) {
196
			$parentControl = $tplControl;
197
		}
198
		if (($page = $tplControl->getPage()) === null) {
199
			$page = $this->getService()->getRequestedPage();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Prado\IService as the method getRequestedPage() does only exist in the following implementations of said interface: Prado\Web\Services\TPageService, Prado\Wsat\TWsatService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
200
		}
201
		$controls = [];
202
		$directChildren = [];
203
		foreach ($this->_tpl as $key => $object) {
204
			if ($object[0] === -1) {
205
				$parent = $parentControl;
206
			} elseif (isset($controls[$object[0]])) {
207
				$parent = $controls[$object[0]];
208
			} else {
209
				continue;
210
			}
211
			if (isset($object[2])) {	// component
212
				$component = Prado::createComponent($object[1]);
213
				$properties = &$object[2];
214
				if ($component instanceof TControl) {
215
					if ($component instanceof \Prado\Web\UI\WebControls\TOutputCache) {
216
						$component->setCacheKeyPrefix($this->_hashCode . $key);
217
					}
218
					$component->setTemplateControl($tplControl);
0 ignored issues
show
Compatibility introduced by
$tplControl of type object<Prado\Web\UI\TControl> is not a sub-type of object<Prado\Web\UI\TTemplateControl>. It seems like you assume a child class of the class Prado\Web\UI\TControl to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
219
					if (isset($properties['id'])) {
220 View Code Duplication
						if (is_array($properties['id'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
							$properties['id'] = $component->evaluateExpression($properties['id'][1]);
222
						}
223
						$tplControl->registerObject($properties['id'], $component);
224
					}
225
					if (isset($properties['skinid'])) {
226
						if (is_array($properties['skinid'])) {
227
							$component->setSkinID($component->evaluateExpression($properties['skinid'][1]));
228
						} else {
229
							$component->setSkinID($properties['skinid']);
230
						}
231
						unset($properties['skinid']);
232
					}
233
234
					$component->trackViewState(false);
235
236
					$component->applyStyleSheetSkin($page);
237
					foreach ($properties as $name => $value) {
238
						$this->configureControl($component, $name, $value);
239
					}
240
241
					$component->trackViewState(true);
242
243
					if ($parent === $parentControl) {
244
						$directChildren[] = $component;
245
					} else {
246
						$component->createdOnTemplate($parent);
247
					}
248
					if ($component->getAllowChildControls()) {
249
						$controls[$key] = $component;
250
					}
251
				} elseif ($component instanceof TComponent) {
252
					$controls[$key] = $component;
253
					if (isset($properties['id'])) {
254 View Code Duplication
						if (is_array($properties['id'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
255
							$properties['id'] = $component->evaluateExpression($properties['id'][1]);
256
						}
257
						$tplControl->registerObject($properties['id'], $component);
258
						if (!$component->hasProperty('id')) {
259
							unset($properties['id']);
260
						}
261
					}
262
					foreach ($properties as $name => $value) {
263
						$this->configureComponent($component, $name, $value);
264
					}
265
					if ($parent === $parentControl) {
266
						$directChildren[] = $component;
267
					} else {
268
						$component->createdOnTemplate($parent);
269
					}
270
				}
271
			} else {
272
				if ($object[1] instanceof TCompositeLiteral) {
273
					// need to clone a new object because the one in template is reused
274
					$o = clone $object[1];
275
					$o->setContainer($tplControl);
276
					if ($parent === $parentControl) {
277
						$directChildren[] = $o;
278
					} else {
279
						$parent->addParsedObject($o);
280
					}
281
				} else {
282
					if ($parent === $parentControl) {
283
						$directChildren[] = $object[1];
284
					} else {
285
						$parent->addParsedObject($object[1]);
286
					}
287
				}
288
			}
289
		}
290
		// delay setting parent till now because the parent may cause
291
		// the child to do lifecycle catchup which may cause problem
292
		// if the child needs its own child controls.
293
		foreach ($directChildren as $control) {
294
			if ($control instanceof TComponent) {
295
				$control->createdOnTemplate($parentControl);
296
			} else {
297
				$parentControl->addParsedObject($control);
298
			}
299
		}
300
	}
301
302
	/**
303
	 * Configures a property/event of a control.
304
	 * @param TControl $control control to be configured
305
	 * @param string $name property name
306
	 * @param mixed $value property initial value
307
	 */
308
	protected function configureControl($control, $name, $value)
309
	{
310
		if (strncasecmp($name, 'on', 2) === 0) {		// is an event
311
			$this->configureEvent($control, $name, $value, $control);
312
		} elseif (($pos = strrpos($name, '.')) === false) {	// is a simple property or custom attribute
313
			$this->configureProperty($control, $name, $value);
314
		} else {	// is a subproperty
315
			$this->configureSubProperty($control, $name, $value);
316
		}
317
	}
318
319
	/**
320
	 * Configures a property of a non-control component.
321
	 * @param TComponent $component component to be configured
322
	 * @param string $name property name
323
	 * @param mixed $value property initial value
324
	 */
325
	protected function configureComponent($component, $name, $value)
326
	{
327
		if (strpos($name, '.') === false) {	// is a simple property or custom attribute
328
			$this->configureProperty($component, $name, $value);
329
		} else {	// is a subproperty
330
			$this->configureSubProperty($component, $name, $value);
331
		}
332
	}
333
334
	/**
335
	 * Configures an event for a control.
336
	 * @param TControl $control control to be configured
337
	 * @param string $name event name
338
	 * @param string $value event handler
339
	 * @param TControl $contextControl context control
340
	 */
341
	protected function configureEvent($control, $name, $value, $contextControl)
342
	{
343
		if (strpos($value, '.') === false) {
344
			$control->attachEventHandler($name, [$contextControl, 'TemplateControl.' . $value]);
345
		} else {
346
			$control->attachEventHandler($name, [$contextControl, $value]);
347
		}
348
	}
349
350
	/**
351
	 * Configures a simple property for a component.
352
	 * @param TComponent $component component to be configured
353
	 * @param string $name property name
354
	 * @param mixed $value property initial value
355
	 */
356
	protected function configureProperty($component, $name, $value)
357
	{
358
		if (is_array($value)) {
359
			switch ($value[0]) {
360
				case self::CONFIG_DATABIND:
361
					$component->bindProperty($name, $value[1]);
362
					break;
363 View Code Duplication
				case self::CONFIG_EXPRESSION:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
364
					if ($component instanceof TControl) {
365
						$component->autoBindProperty($name, $value[1]);
366
					} else {
367
						$setter = 'set' . $name;
368
						$component->$setter($this->_tplControl->evaluateExpression($value[1]));
369
					}
370
					break;
371
				case self::CONFIG_TEMPLATE:
372
					$setter = 'set' . $name;
373
					$component->$setter($value[1]);
374
					break;
375 View Code Duplication
				case self::CONFIG_ASSET:		// asset URL
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
					$setter = 'set' . $name;
377
					$url = $this->publishFilePath($this->_contextPath . DIRECTORY_SEPARATOR . $value[1]);
378
					$component->$setter($url);
379
					break;
380
				case self::CONFIG_PARAMETER:		// application parameter
381
					$setter = 'set' . $name;
382
					$component->$setter($this->getApplication()->getParameters()->itemAt($value[1]));
383
					break;
384
				case self::CONFIG_LOCALIZATION:
385
					$setter = 'set' . $name;
386
					$component->$setter(Prado::localize($value[1]));
387
					break;
388
				default:	// an error if reaching here
389
					throw new TConfigurationException('template_tag_unexpected', $name, $value[1]);
390
					break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be 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...
391
			}
392
		} else {
393
			if (substr($name, 0, 2) == 'js') {
394
				if ($value and !($value instanceof TJavaScriptLiteral)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
395
					$value = new TJavaScriptLiteral($value);
396
				}
397
			}
398
			$setter = 'set' . $name;
399
			$component->$setter($value);
400
		}
401
	}
402
403
	/**
404
	 * Configures a subproperty for a component.
405
	 * @param TComponent $component component to be configured
406
	 * @param string $name subproperty name
407
	 * @param mixed $value subproperty initial value
408
	 */
409
	protected function configureSubProperty($component, $name, $value)
410
	{
411
		if (is_array($value)) {
412
			switch ($value[0]) {
413
				case self::CONFIG_DATABIND:		// databinding
414
					$component->bindProperty($name, $value[1]);
415
					break;
416 View Code Duplication
				case self::CONFIG_EXPRESSION:		// expression
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
417
					if ($component instanceof TControl) {
418
						$component->autoBindProperty($name, $value[1]);
419
					} else {
420
						$component->setSubProperty($name, $this->_tplControl->evaluateExpression($value[1]));
421
					}
422
					break;
423
				case self::CONFIG_TEMPLATE:
424
					$component->setSubProperty($name, $value[1]);
425
					break;
426 View Code Duplication
				case self::CONFIG_ASSET:		// asset URL
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
427
					$url = $this->publishFilePath($this->_contextPath . DIRECTORY_SEPARATOR . $value[1]);
428
					$component->setSubProperty($name, $url);
429
					break;
430
				case self::CONFIG_PARAMETER:		// application parameter
431
					$component->setSubProperty($name, $this->getApplication()->getParameters()->itemAt($value[1]));
432
					break;
433
				case self::CONFIG_LOCALIZATION:
434
					$component->setSubProperty($name, Prado::localize($value[1]));
435
					break;
436
				default:	// an error if reaching here
437
					throw new TConfigurationException('template_tag_unexpected', $name, $value[1]);
438
					break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be 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...
439
			}
440
		} else {
441
			$component->setSubProperty($name, $value);
442
		}
443
	}
444
445
	/**
446
	 * Parses a template string.
447
	 *
448
	 * This template parser recognizes five types of data:
449
	 * regular string, well-formed component tags, well-formed property tags, directives, and expressions.
450
	 *
451
	 * The parsing result is returned as an array. Each array element can be of three types:
452
	 * - a string, 0: container index; 1: string content;
453
	 * - a component tag, 0: container index; 1: component type; 2: attributes (name=>value pairs)
454
	 * If a directive is found in the template, it will be parsed and can be
455
	 * retrieved via {@link getDirective}, which returns an array consisting of
456
	 * name-value pairs in the directive.
457
	 *
458
	 * Note, attribute names are treated as case-insensitive and will be turned into lower cases.
459
	 * Component and directive types are case-sensitive.
460
	 * Container index is the index to the array element that stores the container object.
461
	 * If an object has no container, its container index is -1.
462
	 *
463
	 * @param string $input the template string
464
	 * @throws TConfigurationException if a parsing error is encountered
465
	 */
466
	protected function parse($input)
467
	{
468
		$input = $this->preprocess($input);
469
		$tpl = &$this->_tpl;
470
		$n = preg_match_all(self::REGEX_RULES, $input, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
471
		$expectPropEnd = false;
472
		$textStart = 0;
473
		$stack = [];
474
		$container = -1;
475
		$matchEnd = 0;
476
		$c = 0;
477
		$this->_directive = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_directive.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
478
		try {
479
			for ($i = 0;$i < $n;++$i) {
480
				$match = &$matches[$i];
481
				$str = $match[0][0];
482
				$matchStart = $match[0][1];
483
				$matchEnd = $matchStart + strlen($str) - 1;
484
				if (strpos($str, '<com:') === 0) {	// opening component tag
485
					if ($expectPropEnd) {
486
						continue;
487
					}
488 View Code Duplication
					if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
489
						$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
490
					}
491
					$textStart = $matchEnd + 1;
492
					$type = $match[1][0];
493
					$attributes = $this->parseAttributes($match[2][0], $match[2][1]);
494
					$class = $this->validateAttributes($type, $attributes);
495
					$tpl[$c++] = [$container, $class, $attributes];
496
					if ($str[strlen($str) - 2] !== '/') {  // open tag
497
						$stack[] = $type;
498
						$container = $c - 1;
499
					}
500
				} elseif (strpos($str, '</com:') === 0) {	// closing component tag
501
					if ($expectPropEnd) {
502
						continue;
503
					}
504 View Code Duplication
					if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
505
						$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
506
					}
507
					$textStart = $matchEnd + 1;
508
					$type = $match[1][0];
509
510
					if (empty($stack)) {
511
						throw new TConfigurationException('template_closingtag_unexpected', "</com:$type>");
512
					}
513
514
					$name = array_pop($stack);
515
					if ($name !== $type) {
516
						$tag = $name[0] === '@' ? '</prop:' . substr($name, 1) . '>' : "</com:$name>";
517
						throw new TConfigurationException('template_closingtag_expected', $tag, "</com:$type>");
518
					}
519
					$container = $tpl[$container][0];
520
				} elseif (strpos($str, '<%@') === 0) {	// directive
521
					if ($expectPropEnd) {
522
						continue;
523
					}
524 View Code Duplication
					if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
525
						$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
526
					}
527
					$textStart = $matchEnd + 1;
528
					if (isset($tpl[0]) || $this->_directive !== null) {
529
						throw new TConfigurationException('template_directive_nonunique');
530
					}
531
					$this->_directive = $this->parseAttributes($match[4][0], $match[4][1]);
532
				} elseif (strpos($str, '<%') === 0) {	// expression
533
					if ($expectPropEnd) {
534
						continue;
535
					}
536 View Code Duplication
					if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
537
						$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
538
					}
539
					$textStart = $matchEnd + 1;
540
					$literal = trim($match[5][0]);
541
					if ($str[2] === '=') {	// expression
542
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_EXPRESSION, $literal]];
543 View Code Duplication
					} elseif ($str[2] === '%') {  // statements
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
544
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_STATEMENTS, $literal]];
545
					} elseif ($str[2] === '#') {
546
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_DATABINDING, $literal]];
547 View Code Duplication
					} elseif ($str[2] === '$') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
548
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_EXPRESSION, "\$this->getApplication()->getParameters()->itemAt('$literal')"]];
549
					} elseif ($str[2] === '~') {
550
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_EXPRESSION, "\$this->publishFilePath('$this->_contextPath/$literal')"]];
551 View Code Duplication
					} elseif ($str[2] === '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
552
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_EXPRESSION, "rtrim(dirname(\$this->getApplication()->getRequest()->getApplicationUrl()), '\/').'/$literal'"]];
553
					} elseif ($str[2] === '[') {
554
						$literal = strtr(trim(substr($literal, 0, strlen($literal) - 1)), ["'" => "\'", "\\" => "\\\\"]);
555
						$tpl[$c++] = [$container, [TCompositeLiteral::TYPE_EXPRESSION, "Prado::localize('$literal')"]];
556
					}
557
				} elseif (strpos($str, '<prop:') === 0) {	// opening property
558
					if (strrpos($str, '/>') === strlen($str) - 2) {  //subproperties
559
						if ($expectPropEnd) {
560
							continue;
561
						}
562 View Code Duplication
						if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
563
							$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
564
						}
565
						$textStart = $matchEnd + 1;
566
						$prop = strtolower($match[6][0]);
567
						$attrs = $this->parseAttributes($match[7][0], $match[7][1]);
568
						$attributes = [];
569
						foreach ($attrs as $name => $value) {
570
							$attributes[$prop . '.' . $name] = $value;
571
						}
572
						$type = $tpl[$container][1];
573
						$this->validateAttributes($type, $attributes);
574
						foreach ($attributes as $name => $value) {
575
							if (isset($tpl[$container][2][$name])) {
576
								throw new TConfigurationException('template_property_duplicated', $name);
577
							}
578
							$tpl[$container][2][$name] = $value;
579
						}
580
					} else {  // regular property
581
						$prop = strtolower($match[3][0]);
582
						$stack[] = '@' . $prop;
583
						if (!$expectPropEnd) {
584 View Code Duplication
							if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
								$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
586
							}
587
							$textStart = $matchEnd + 1;
588
							$expectPropEnd = true;
589
						}
590
					}
591
				} elseif (strpos($str, '</prop:') === 0) {	// closing property
592
					$prop = strtolower($match[3][0]);
593
					if (empty($stack)) {
594
						throw new TConfigurationException('template_closingtag_unexpected', "</prop:$prop>");
595
					}
596
					$name = array_pop($stack);
597 View Code Duplication
					if ($name !== '@' . $prop) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
598
						$tag = $name[0] === '@' ? '</prop:' . substr($name, 1) . '>' : "</com:$name>";
599
						throw new TConfigurationException('template_closingtag_expected', $tag, "</prop:$prop>");
600
					}
601
					if (($last = count($stack)) < 1 || $stack[$last - 1][0] !== '@') {
602
						if ($matchStart > $textStart) {
603
							$value = substr($input, $textStart, $matchStart - $textStart);
604
							if (substr($prop, -8, 8) === 'template') {
605
								$value = $this->parseTemplateProperty($value, $textStart);
606
							} else {
607
								$value = $this->parseAttribute($value);
608
							}
609
							if ($container >= 0) {
610
								$type = $tpl[$container][1];
611
								$this->validateAttributes($type, [$prop => $value]);
612
								if (isset($tpl[$container][2][$prop])) {
613
									throw new TConfigurationException('template_property_duplicated', $prop);
614
								}
615
								$tpl[$container][2][$prop] = $value;
616
							} else {	// a property for the template control
617
								$this->_directive[$prop] = $value;
618
							}
619
							$textStart = $matchEnd + 1;
620
						}
621
						$expectPropEnd = false;
622
					}
623
				} elseif (strpos($str, '<!--') === 0) {	// comments
624
					if ($expectPropEnd) {
625
						throw new TConfigurationException('template_comments_forbidden');
626
					}
627 View Code Duplication
					if ($matchStart > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
628
						$tpl[$c++] = [$container, substr($input, $textStart, $matchStart - $textStart)];
629
					}
630
					$textStart = $matchEnd + 1;
631
				} else {
632
					throw new TConfigurationException('template_matching_unexpected', $match);
633
				}
634
			}
635 View Code Duplication
			if (!empty($stack)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
636
				$name = array_pop($stack);
637
				$tag = $name[0] === '@' ? '</prop:' . substr($name, 1) . '>' : "</com:$name>";
638
				throw new TConfigurationException('template_closingtag_expected', $tag, "nothing");
639
			}
640 View Code Duplication
			if ($textStart < strlen($input)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
641
				$tpl[$c++] = [$container, substr($input, $textStart)];
642
			}
643
		} catch (\Exception $e) {
644
			if (($e instanceof TException) && ($e instanceof TTemplateException)) {
0 ignored issues
show
Bug introduced by
The class Prado\Web\UI\TException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
645
				throw $e;
646
			}
647
			if ($matchEnd === 0) {
648
				$line = $this->_startingLine + 1;
649
			} else {
650
				$line = $this->_startingLine + count(explode("\n", substr($input, 0, $matchEnd + 1)));
651
			}
652
			$this->handleException($e, $line, $input);
653
		}
654
655
		if ($this->_directive === null) {
656
			$this->_directive = [];
657
		}
658
659
		// optimization by merging consecutive strings, expressions, statements and bindings
660
		$objects = [];
661
		$parent = null;
662
		$merged = [];
663
		foreach ($tpl as $id => $object) {
664
			if (isset($object[2]) || $object[0] !== $parent) {
665 View Code Duplication
				if ($parent !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
666
					if (count($merged[1]) === 1 && is_string($merged[1][0])) {
667
						$objects[$id - 1] = [$merged[0], $merged[1][0]];
668
					} else {
669
						$objects[$id - 1] = [$merged[0], new TCompositeLiteral($merged[1])];
670
					}
671
				}
672
				if (isset($object[2])) {
673
					$parent = null;
674
					$objects[$id] = $object;
675
				} else {
676
					$parent = $object[0];
677
					$merged = [$parent, [$object[1]]];
678
				}
679
			} else {
680
				$merged[1][] = $object[1];
681
			}
682
		}
683 View Code Duplication
		if ($parent !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
684
			if (count($merged[1]) === 1 && is_string($merged[1][0])) {
685
				$objects[$id] = [$merged[0], $merged[1][0]];
0 ignored issues
show
Bug introduced by
The variable $id seems to be defined by a foreach iteration on line 663. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
686
			} else {
687
				$objects[$id] = [$merged[0], new TCompositeLiteral($merged[1])];
688
			}
689
		}
690
		$tpl = $objects;
691
		return $objects;
692
	}
693
694
	/**
695
	 * Parses the attributes of a tag from a string.
696
	 * @param string $str the string to be parsed.
697
	 * @param mixed $offset
698
	 * @return array attribute values indexed by names.
699
	 */
700
	protected function parseAttributes($str, $offset)
701
	{
702
		if ($str === '') {
703
			return [];
704
		}
705
		$pattern = '/([\w\.\-]+)\s*=\s*(\'.*?\'|".*?"|<%.*?%>)/msS';
706
		$attributes = [];
707
		$n = preg_match_all($pattern, $str, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
708
		for ($i = 0;$i < $n;++$i) {
709
			$match = &$matches[$i];
710
			$name = strtolower($match[1][0]);
711
			if (isset($attributes[$name])) {
712
				throw new TConfigurationException('template_property_duplicated', $name);
713
			}
714
			$value = $match[2][0];
715
			if (substr($name, -8, 8) === 'template') {
716
				if ($value[0] === '\'' || $value[0] === '"') {
717
					$attributes[$name] = $this->parseTemplateProperty(substr($value, 1, strlen($value) - 2), $match[2][1] + 1);
718
				} else {
719
					$attributes[$name] = $this->parseTemplateProperty($value, $match[2][1]);
720
				}
721
			} else {
722
				if ($value[0] === '\'' || $value[0] === '"') {
723
					$attributes[$name] = $this->parseAttribute(substr($value, 1, strlen($value) - 2));
724
				} else {
725
					$attributes[$name] = $this->parseAttribute($value);
726
				}
727
			}
728
		}
729
		return $attributes;
730
	}
731
732
	protected function parseTemplateProperty($content, $offset)
733
	{
734
		$line = $this->_startingLine + count(explode("\n", substr($this->_content, 0, $offset))) - 1;
735
		return [self::CONFIG_TEMPLATE, new TTemplate($content, $this->_contextPath, $this->_tplFile, $line, false)];
736
	}
737
738
	/**
739
	 * Parses a single attribute.
740
	 * @param string $value the string to be parsed.
741
	 * @return array attribute initialization
742
	 */
743
	protected function parseAttribute($value)
744
	{
745
		if (($n = preg_match_all('/<%[#=].*?%>/msS', $value, $matches, PREG_OFFSET_CAPTURE)) > 0) {
746
			$isDataBind = false;
747
			$textStart = 0;
748
			$expr = '';
749
			for ($i = 0;$i < $n;++$i) {
750
				$match = $matches[0][$i];
751
				$token = $match[0];
752
				$offset = $match[1];
753
				$length = strlen($token);
754
				if ($token[2] === '#') {
755
					$isDataBind = true;
756
				}
757 View Code Duplication
				if ($offset > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
758
					$expr .= ".'" . strtr(substr($value, $textStart, $offset - $textStart), ["'" => "\\'", "\\" => "\\\\"]) . "'";
759
				}
760
				$expr .= '.(' . substr($token, 3, $length - 5) . ')';
761
				$textStart = $offset + $length;
762
			}
763
			$length = strlen($value);
764 View Code Duplication
			if ($length > $textStart) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
765
				$expr .= ".'" . strtr(substr($value, $textStart, $length - $textStart), ["'" => "\\'", "\\" => "\\\\"]) . "'";
766
			}
767
			if ($isDataBind) {
768
				return [self::CONFIG_DATABIND, ltrim($expr, '.')];
769
			} else {
770
				return [self::CONFIG_EXPRESSION, ltrim($expr, '.')];
771
			}
772
		} elseif (preg_match('/\\s*(<%~.*?%>|<%\\$.*?%>|<%\\[.*?\\]%>|<%\/.*?%>)\\s*/msS', $value, $matches) && $matches[0] === $value) {
773
			$value = $matches[1];
774
			if ($value[2] === '~') {
775
				return [self::CONFIG_ASSET, trim(substr($value, 3, strlen($value) - 5))];
776 View Code Duplication
			} elseif ($value[2] === '[') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
777
				return [self::CONFIG_LOCALIZATION, trim(substr($value, 3, strlen($value) - 6))];
778
			} elseif ($value[2] === '$') {
779
				return [self::CONFIG_PARAMETER, trim(substr($value, 3, strlen($value) - 5))];
780 View Code Duplication
			} elseif ($value[2] === '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
781
				$literal = trim(substr($value, 3, strlen($value) - 5));
782
				return [self::CONFIG_EXPRESSION, "rtrim(dirname(\$this->getApplication()->getRequest()->getApplicationUrl()), '\/').'/$literal'"];
783
			}
784
		} else {
785
			return $value;
786
		}
787
	}
788
789
	protected function validateAttributes($type, $attributes)
790
	{
791
		Prado::using($type);
792 View Code Duplication
		if (($pos = strrpos($type, '.')) !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
793
			$className = substr($type, $pos + 1);
794
		} else {
795
			$className = $type;
796
		}
797
		$class = new \ReflectionClass($className);
798
		if (is_subclass_of($className, '\Prado\Web\UI\TControl') || $className === '\Prado\Web\UI\TControl') {
799
			foreach ($attributes as $name => $att) {
800
				if (($pos = strpos($name, '.')) !== false) {
801
					// a subproperty, so the first segment must be readable
802
					$subname = substr($name, 0, $pos);
803
					if (!$class->hasMethod('get' . $subname)) {
804
						throw new TConfigurationException('template_property_unknown', $type, $subname);
805
					}
806
				} elseif (strncasecmp($name, 'on', 2) === 0) {
807
					// an event
808
					if (!$class->hasMethod($name)) {
809
						throw new TConfigurationException('template_event_unknown', $type, $name);
810
					} elseif (!is_string($att)) {
811
						throw new TConfigurationException('template_eventhandler_invalid', $type, $name);
812
					}
813
				} else {
814
					// a simple property
815
					if (! ($class->hasMethod('set' . $name) || $class->hasMethod('setjs' . $name) || $this->isClassBehaviorMethod($class, 'set' . $name))) {
816 View Code Duplication
						if ($class->hasMethod('get' . $name) || $class->hasMethod('getjs' . $name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
817
							throw new TConfigurationException('template_property_readonly', $type, $name);
818
						} else {
819
							throw new TConfigurationException('template_property_unknown', $type, $name);
820
						}
821
					} elseif (is_array($att) && $att[0] !== self::CONFIG_EXPRESSION) {
822
						if (strcasecmp($name, 'id') === 0) {
823
							throw new TConfigurationException('template_controlid_invalid', $type);
824
						} elseif (strcasecmp($name, 'skinid') === 0) {
825
							throw new TConfigurationException('template_controlskinid_invalid', $type);
826
						}
827
					}
828
				}
829
			}
830
		} elseif (is_subclass_of($className, '\Prado\TComponent') || $className === '\Prado\TComponent') {
831
			foreach ($attributes as $name => $att) {
832
				if (is_array($att) && ($att[0] === self::CONFIG_DATABIND)) {
833
					throw new TConfigurationException('template_databind_forbidden', $type, $name);
834
				}
835
				if (($pos = strpos($name, '.')) !== false) {
836
					// a subproperty, so the first segment must be readable
837
					$subname = substr($name, 0, $pos);
838
					if (!$class->hasMethod('get' . $subname)) {
839
						throw new TConfigurationException('template_property_unknown', $type, $subname);
840
					}
841
				} elseif (strncasecmp($name, 'on', 2) === 0) {
842
					throw new TConfigurationException('template_event_forbidden', $type, $name);
843
				} else {
844
					// id is still alowed for TComponent, even if id property doesn't exist
845
					if (strcasecmp($name, 'id') !== 0 && !($class->hasMethod('set' . $name) || $this->isClassBehaviorMethod($class, 'set' . $name))) {
846 View Code Duplication
						if ($class->hasMethod('get' . $name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
847
							throw new TConfigurationException('template_property_readonly', $type, $name);
848
						} else {
849
							throw new TConfigurationException('template_property_unknown', $type, $name);
850
						}
851
					}
852
				}
853
			}
854
		} else {
855
			throw new TConfigurationException('template_component_required', $type);
856
		}
857
		return $class->getName();
858
	}
859
860
	/**
861
	 * @return array list of included external template files
862
	 */
863
	public function getIncludedFiles()
864
	{
865
		return $this->_includedFiles;
866
	}
867
868
	/**
869
	 * Handles template parsing exception.
870
	 * This method rethrows the exception caught during template parsing.
871
	 * It adjusts the error location by giving out correct error line number and source file.
872
	 * @param Exception $e template exception
873
	 * @param int $line line number
874
	 * @param null|string $input template string if no source file is used
875
	 */
876
	protected function handleException($e, $line, $input = null)
877
	{
878
		$srcFile = $this->_tplFile;
879
880
		if (($n = count($this->_includedFiles)) > 0) { // need to adjust error row number and file name
881
			for ($i = $n - 1;$i >= 0;--$i) {
882
				if ($this->_includeAtLine[$i] <= $line) {
883
					if ($line < $this->_includeAtLine[$i] + $this->_includeLines[$i]) {
884
						$line = $line - $this->_includeAtLine[$i] + 1;
885
						$srcFile = $this->_includedFiles[$i];
886
						break;
887
					} else {
888
						$line = $line - $this->_includeLines[$i] + 1;
889
					}
890
				}
891
			}
892
		}
893
		$exception = new TTemplateException('template_format_invalid', $e->getMessage());
894
		$exception->setLineNumber($line);
895
		if (!empty($srcFile)) {
896
			$exception->setTemplateFile($srcFile);
897
		} else {
898
			$exception->setTemplateSource($input);
899
		}
900
		throw $exception;
901
	}
902
903
	/**
904
	 * Preprocesses the template string by including external templates
905
	 * @param string $input template string
906
	 * @return string expanded template string
907
	 */
908
	protected function preprocess($input)
909
	{
910
		if ($n = preg_match_all('/<%include(.*?)%>/', $input, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
911
			for ($i = 0;$i < $n;++$i) {
912
				$filePath = Prado::getPathOfNamespace(trim($matches[$i][1][0]), TTemplateManager::TEMPLATE_FILE_EXT);
913
				if ($filePath !== null && is_file($filePath)) {
914
					$this->_includedFiles[] = $filePath;
915
				} else {
916
					$errorLine = count(explode("\n", substr($input, 0, $matches[$i][0][1] + 1)));
917
					$this->handleException(new TConfigurationException('template_include_invalid', trim($matches[$i][1][0])), $errorLine, $input);
918
				}
919
			}
920
			$base = 0;
921
			for ($i = 0;$i < $n;++$i) {
922
				$ext = file_get_contents($this->_includedFiles[$i]);
923
				$length = strlen($matches[$i][0][0]);
924
				$offset = $base + $matches[$i][0][1];
925
				$this->_includeAtLine[$i] = count(explode("\n", substr($input, 0, $offset)));
926
				$this->_includeLines[$i] = count(explode("\n", $ext));
927
				$input = substr_replace($input, $ext, $offset, $length);
928
				$base += strlen($ext) - $length;
929
			}
930
		}
931
932
		return $input;
933
	}
934
935
	/**
936
	 * Checks if the given method belongs to a previously attached class behavior.
937
	 * @param ReflectionClass $class
938
	 * @param string $method
939
	 * @return bool
940
	 */
941
	protected function isClassBehaviorMethod(\ReflectionClass $class, $method)
942
	{
943
		$component = new \ReflectionClass('\Prado\TComponent');
944
		$behaviors = $component->getStaticProperties();
945
		if (!isset($behaviors['_um'])) {
946
			return false;
947
		}
948
		foreach ($behaviors['_um'] as $name => $list) {
0 ignored issues
show
Bug introduced by
The expression $behaviors['_um'] of type string is not traversable.
Loading history...
949
			if (strtolower($class->getShortName()) !== $name && !$class->isSubclassOf($name)) {
950
				continue;
951
			}
952
			foreach ($list as $param) {
953
				if (method_exists($param->getBehavior(), $method)) {
954
					return true;
955
				}
956
			}
957
		}
958
		return false;
959
	}
960
}
961