1: <?php
2: /**
3: * Smarty Internal Plugin CompileBase
4: *
5: * @package Smarty
6: * @subpackage Compiler
7: * @author Uwe Tews
8: */
9:
10: /**
11: * This class does extend all internal compile plugins
12: *
13: * @package Smarty
14: * @subpackage Compiler
15: */
16: abstract class Smarty_Internal_CompileBase
17: {
18: /**
19: * Array of names of required attribute required by tag
20: *
21: * @var array
22: */
23: public $required_attributes = array();
24:
25: /**
26: * Array of names of optional attribute required by tag
27: * use array('_any') if there is no restriction of attributes names
28: *
29: * @var array
30: */
31: public $optional_attributes = array();
32:
33: /**
34: * Shorttag attribute order defined by its names
35: *
36: * @var array
37: */
38: public $shorttag_order = array();
39:
40: /**
41: * Array of names of valid option flags
42: *
43: * @var array
44: */
45: public $option_flags = array('nocache');
46:
47: /**
48: * Mapping array for boolean option value
49: *
50: * @var array
51: */
52: public $optionMap = array(1 => true, 0 => false, 'true' => true, 'false' => false);
53:
54: /**
55: * Mapping array with attributes as key
56: *
57: * @var array
58: */
59: public $mapCache = array();
60:
61: /**
62: * This function checks if the attributes passed are valid
63: * The attributes passed for the tag to compile are checked against the list of required and
64: * optional attributes. Required attributes must be present. Optional attributes are check against
65: * the corresponding list. The keyword '_any' specifies that any attribute will be accepted
66: * as valid
67: *
68: * @param object $compiler compiler object
69: * @param array $attributes attributes applied to the tag
70: *
71: * @return array of mapped attributes for further processing
72: */
73: public function getAttributes($compiler, $attributes)
74: {
75: $_indexed_attr = array();
76: if (!isset($this->mapCache[ 'option' ])) {
77: $this->mapCache[ 'option' ] = array_fill_keys($this->option_flags, true);
78: }
79: foreach ($attributes as $key => $mixed) {
80: // shorthand ?
81: if (!is_array($mixed)) {
82: // option flag ?
83: if (isset($this->mapCache[ 'option' ][ trim($mixed, '\'"') ])) {
84: $_indexed_attr[ trim($mixed, '\'"') ] = true;
85: // shorthand attribute ?
86: } elseif (isset($this->shorttag_order[ $key ])) {
87: $_indexed_attr[ $this->shorttag_order[ $key ] ] = $mixed;
88: } else {
89: // too many shorthands
90: $compiler->trigger_template_error('too many shorthand attributes', null, true);
91: }
92: // named attribute
93: } else {
94: foreach ($mixed as $k => $v) {
95: // option flag?
96: if (isset($this->mapCache[ 'option' ][ $k ])) {
97: if (is_bool($v)) {
98: $_indexed_attr[ $k ] = $v;
99: } else {
100: if (is_string($v)) {
101: $v = trim($v, '\'" ');
102: }
103: if (isset($this->optionMap[ $v ])) {
104: $_indexed_attr[ $k ] = $this->optionMap[ $v ];
105: } else {
106: $compiler->trigger_template_error(
107: "illegal value '" . var_export($v, true) .
108: "' for option flag '{$k}'",
109: null,
110: true
111: );
112: }
113: }
114: // must be named attribute
115: } else {
116: $_indexed_attr[ $k ] = $v;
117: }
118: }
119: }
120: }
121: // check if all required attributes present
122: foreach ($this->required_attributes as $attr) {
123: if (!isset($_indexed_attr[ $attr ])) {
124: $compiler->trigger_template_error("missing '{$attr}' attribute", null, true);
125: }
126: }
127: // check for not allowed attributes
128: if ($this->optional_attributes !== array('_any')) {
129: if (!isset($this->mapCache[ 'all' ])) {
130: $this->mapCache[ 'all' ] =
131: array_fill_keys(
132: array_merge(
133: $this->required_attributes,
134: $this->optional_attributes,
135: $this->option_flags
136: ),
137: true
138: );
139: }
140: foreach ($_indexed_attr as $key => $dummy) {
141: if (!isset($this->mapCache[ 'all' ][ $key ]) && $key !== 0) {
142: $compiler->trigger_template_error("unexpected '{$key}' attribute", null, true);
143: }
144: }
145: }
146: // default 'false' for all option flags not set
147: foreach ($this->option_flags as $flag) {
148: if (!isset($_indexed_attr[ $flag ])) {
149: $_indexed_attr[ $flag ] = false;
150: }
151: }
152: if (isset($_indexed_attr[ 'nocache' ]) && $_indexed_attr[ 'nocache' ]) {
153: $compiler->tag_nocache = true;
154: }
155: return $_indexed_attr;
156: }
157:
158: /**
159: * Push opening tag name on stack
160: * Optionally additional data can be saved on stack
161: *
162: * @param object $compiler compiler object
163: * @param string $openTag the opening tag's name
164: * @param mixed $data optional data saved
165: */
166: public function openTag($compiler, $openTag, $data = null)
167: {
168: array_push($compiler->_tag_stack, array($openTag, $data));
169: }
170:
171: /**
172: * Pop closing tag
173: * Raise an error if this stack-top doesn't match with expected opening tags
174: *
175: * @param object $compiler compiler object
176: * @param array|string $expectedTag the expected opening tag names
177: *
178: * @return mixed any type the opening tag's name or saved data
179: */
180: public function closeTag($compiler, $expectedTag)
181: {
182: if (count($compiler->_tag_stack) > 0) {
183: // get stacked info
184: list($_openTag, $_data) = array_pop($compiler->_tag_stack);
185: // open tag must match with the expected ones
186: if (in_array($_openTag, (array)$expectedTag)) {
187: if (is_null($_data)) {
188: // return opening tag
189: return $_openTag;
190: } else {
191: // return restored data
192: return $_data;
193: }
194: }
195: // wrong nesting of tags
196: $compiler->trigger_template_error("unclosed '{$compiler->smarty->left_delimiter}{$_openTag}{$compiler->smarty->right_delimiter}' tag");
197: return;
198: }
199: // wrong nesting of tags
200: $compiler->trigger_template_error('unexpected closing tag', null, true);
201: return;
202: }
203: }
204: