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: * Array of names of optional attribute required by tag
26: * use array('_any') if there is no restriction of attributes names
27: *
28: * @var array
29: */
30: public $optional_attributes = array();
31: /**
32: * Shorttag attribute order defined by its names
33: *
34: * @var array
35: */
36: public $shorttag_order = array();
37: /**
38: * Array of names of valid option flags
39: *
40: * @var array
41: */
42: public $option_flags = array('nocache');
43:
44: /**
45: * This function checks if the attributes passed are valid
46: * The attributes passed for the tag to compile are checked against the list of required and
47: * optional attributes. Required attributes must be present. Optional attributes are check against
48: * the corresponding list. The keyword '_any' specifies that any attribute will be accepted
49: * as valid
50: *
51: * @param object $compiler compiler object
52: * @param array $attributes attributes applied to the tag
53: *
54: * @return array of mapped attributes for further processing
55: */
56: public function getAttributes($compiler, $attributes)
57: {
58: $_indexed_attr = array();
59: // loop over attributes
60: foreach ($attributes as $key => $mixed) {
61: // shorthand ?
62: if (!is_array($mixed)) {
63: // option flag ?
64: if (in_array(trim($mixed, '\'"'), $this->option_flags)) {
65: $_indexed_attr[trim($mixed, '\'"')] = true;
66: // shorthand attribute ?
67: } elseif (isset($this->shorttag_order[$key])) {
68: $_indexed_attr[$this->shorttag_order[$key]] = $mixed;
69: } else {
70: // too many shorthands
71: $compiler->trigger_template_error('too many shorthand attributes', $compiler->lex->taglineno);
72: }
73: // named attribute
74: } else {
75: $kv = each($mixed);
76: // option flag?
77: if (in_array($kv['key'], $this->option_flags)) {
78: if (is_bool($kv['value'])) {
79: $_indexed_attr[$kv['key']] = $kv['value'];
80: } elseif (is_string($kv['value']) && in_array(trim($kv['value'], '\'"'), array('true', 'false'))) {
81: if (trim($kv['value']) == 'true') {
82: $_indexed_attr[$kv['key']] = true;
83: } else {
84: $_indexed_attr[$kv['key']] = false;
85: }
86: } elseif (is_numeric($kv['value']) && in_array($kv['value'], array(0, 1))) {
87: if ($kv['value'] == 1) {
88: $_indexed_attr[$kv['key']] = true;
89: } else {
90: $_indexed_attr[$kv['key']] = false;
91: }
92: } else {
93: $compiler->trigger_template_error("illegal value of option flag \"{$kv['key']}\"", $compiler->lex->taglineno);
94: }
95: // must be named attribute
96: } else {
97: reset($mixed);
98: $_indexed_attr[key($mixed)] = $mixed[key($mixed)];
99: }
100: }
101: }
102: // check if all required attributes present
103: foreach ($this->required_attributes as $attr) {
104: if (!array_key_exists($attr, $_indexed_attr)) {
105: $compiler->trigger_template_error("missing \"" . $attr . "\" attribute", $compiler->lex->taglineno);
106: }
107: }
108: // check for not allowed attributes
109: if ($this->optional_attributes != array('_any')) {
110: $tmp_array = array_merge($this->required_attributes, $this->optional_attributes, $this->option_flags);
111: foreach ($_indexed_attr as $key => $dummy) {
112: if (!in_array($key, $tmp_array) && $key !== 0) {
113: $compiler->trigger_template_error("unexpected \"" . $key . "\" attribute", $compiler->lex->taglineno);
114: }
115: }
116: }
117: // default 'false' for all option flags not set
118: foreach ($this->option_flags as $flag) {
119: if (!isset($_indexed_attr[$flag])) {
120: $_indexed_attr[$flag] = false;
121: }
122: }
123:
124: return $_indexed_attr;
125: }
126:
127: /**
128: * Push opening tag name on stack
129: * Optionally additional data can be saved on stack
130: *
131: * @param object $compiler compiler object
132: * @param string $openTag the opening tag's name
133: * @param mixed $data optional data saved
134: */
135: public function openTag($compiler, $openTag, $data = null)
136: {
137: array_push($compiler->_tag_stack, array($openTag, $data));
138: }
139:
140: /**
141: * Pop closing tag
142: * Raise an error if this stack-top doesn't match with expected opening tags
143: *
144: * @param object $compiler compiler object
145: * @param array|string $expectedTag the expected opening tag names
146: *
147: * @return mixed any type the opening tag's name or saved data
148: */
149: public function closeTag($compiler, $expectedTag)
150: {
151: if (count($compiler->_tag_stack) > 0) {
152: // get stacked info
153: list($_openTag, $_data) = array_pop($compiler->_tag_stack);
154: // open tag must match with the expected ones
155: if (in_array($_openTag, (array) $expectedTag)) {
156: if (is_null($_data)) {
157: // return opening tag
158: return $_openTag;
159: } else {
160: // return restored data
161: return $_data;
162: }
163: }
164: // wrong nesting of tags
165: $compiler->trigger_template_error("unclosed {$compiler->smarty->left_delimiter}" . $_openTag . "{$compiler->smarty->right_delimiter} tag");
166:
167: return;
168: }
169: // wrong nesting of tags
170: $compiler->trigger_template_error("unexpected closing tag", $compiler->lex->taglineno);
171:
172: return;
173: }
174: }
175: