1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte\Runtime;
9:
10: use Latte\Engine;
11: use Latte\Helpers;
12:
13:
14: 15: 16: 17:
18: class FilterExecutor
19: {
20:
21: private $_dynamic = [];
22:
23:
24: private $_static = [
25: 'breaklines' => ['Latte\Runtime\Filters::breaklines', false],
26: 'bytes' => ['Latte\Runtime\Filters::bytes', false],
27: 'capitalize' => ['Latte\Runtime\Filters::capitalize', false],
28: 'datastream' => ['Latte\Runtime\Filters::dataStream', false],
29: 'date' => ['Latte\Runtime\Filters::date', false],
30: 'escapecss' => ['Latte\Runtime\Filters::escapeCss', false],
31: 'escapehtml' => ['Latte\Runtime\Filters::escapeHtml', false],
32: 'escapehtmlcomment' => ['Latte\Runtime\Filters::escapeHtmlComment', false],
33: 'escapeical' => ['Latte\Runtime\Filters::escapeICal', false],
34: 'escapejs' => ['Latte\Runtime\Filters::escapeJs', false],
35: 'escapeurl' => ['rawurlencode', false],
36: 'escapexml' => ['Latte\Runtime\Filters::escapeXml', false],
37: 'firstupper' => ['Latte\Runtime\Filters::firstUpper', false],
38: 'checkurl' => ['Latte\Runtime\Filters::safeUrl', false],
39: 'implode' => ['implode', false],
40: 'indent' => ['Latte\Runtime\Filters::indent', true],
41: 'length' => ['Latte\Runtime\Filters::length', false],
42: 'lower' => ['Latte\Runtime\Filters::lower', false],
43: 'nl2br' => ['Latte\Runtime\Filters::nl2br', false],
44: 'number' => ['number_format', false],
45: 'padleft' => ['Latte\Runtime\Filters::padLeft', false],
46: 'padright' => ['Latte\Runtime\Filters::padRight', false],
47: 'repeat' => ['Latte\Runtime\Filters::repeat', true],
48: 'replace' => ['Latte\Runtime\Filters::replace', true],
49: 'replacere' => ['Latte\Runtime\Filters::replaceRe', false],
50: 'reverse' => ['Latte\Runtime\Filters::reverse', false],
51: 'safeurl' => ['Latte\Runtime\Filters::safeUrl', false],
52: 'strip' => ['Latte\Runtime\Filters::strip', true],
53: 'striphtml' => ['Latte\Runtime\Filters::stripHtml', true],
54: 'striptags' => ['Latte\Runtime\Filters::stripTags', true],
55: 'substr' => ['Latte\Runtime\Filters::substring', false],
56: 'trim' => ['Latte\Runtime\Filters::trim', true],
57: 'truncate' => ['Latte\Runtime\Filters::truncate', false],
58: 'upper' => ['Latte\Runtime\Filters::upper', false],
59: 'webalize' => ['Nette\Utils\Strings::webalize', false],
60: ];
61:
62:
63: 64: 65: 66: 67: 68:
69: public function add($name, $callback)
70: {
71: if ($name == null) {
72: array_unshift($this->_dynamic, $callback);
73: } else {
74: $name = strtolower($name);
75: $this->_static[$name] = [$callback, null];
76: unset($this->$name);
77: }
78: return $this;
79: }
80:
81:
82: 83: 84: 85:
86: public function getAll()
87: {
88: return array_combine($tmp = array_keys($this->_static), $tmp);
89: }
90:
91:
92: 93: 94: 95:
96: public function __get($name)
97: {
98: $lname = strtolower($name);
99: if (isset($this->$lname)) {
100: return $this->$lname;
101:
102: } elseif (isset($this->_static[$lname])) {
103: list($callback, $aware) = $this->prepareFilter($lname);
104: if ($aware) {
105: return $this->$lname = function ($arg) use ($callback) {
106: $args = func_get_args();
107: array_unshift($args, $info = new FilterInfo);
108: if ($arg instanceof IHtmlString) {
109: $args[1] = $arg->__toString();
110: $info->contentType = Engine::CONTENT_HTML;
111: }
112: $res = call_user_func_array($callback, $args);
113: return $info->contentType === Engine::CONTENT_HTML
114: ? new Html($res)
115: : $res;
116: };
117: } else {
118: return $this->$lname = $callback;
119: }
120: }
121:
122: return $this->$lname = function ($arg) use ($lname, $name) {
123: $args = func_get_args();
124: array_unshift($args, $lname);
125: foreach ($this->_dynamic as $filter) {
126: $res = call_user_func_array(Helpers::checkCallback($filter), $args);
127: if ($res !== null) {
128: return $res;
129: } elseif (isset($this->_static[$lname])) {
130: $this->$name = Helpers::checkCallback($this->_static[$lname][0]);
131: return call_user_func_array($this->$name, func_get_args());
132: }
133: }
134: $hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name)) ? ", did you mean '$t'?" : '.';
135: throw new \LogicException("Filter '$name' is not defined$hint");
136: };
137: }
138:
139:
140: 141: 142: 143:
144: public function filterContent($name, FilterInfo $info, $arg)
145: {
146: $lname = strtolower($name);
147: $args = func_get_args();
148: array_shift($args);
149:
150: if (!isset($this->_static[$lname])) {
151: $hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name)) ? ", did you mean '$t'?" : '.';
152: throw new \LogicException("Filter |$name is not defined$hint");
153: }
154:
155: list($callback, $aware) = $this->prepareFilter($lname);
156: if ($aware) {
157: return call_user_func_array($callback, $args);
158:
159: } else {
160: array_shift($args);
161: if ($info->contentType !== Engine::CONTENT_TEXT) {
162: trigger_error("Filter |$name is called with incompatible content type " . strtoupper($info->contentType)
163: . ($info->contentType === Engine::CONTENT_HTML ? ', try to prepend |stripHtml.' : '.'), E_USER_WARNING);
164: }
165: $res = call_user_func_array($this->$name, $args);
166: if ($res instanceof IHtmlString) {
167: trigger_error("Filter |$name should be changed to content-aware filter.");
168: $info->contentType = Engine::CONTENT_HTML;
169: $res = $res->__toString();
170: }
171: return $res;
172: }
173: }
174:
175:
176: private function prepareFilter($name)
177: {
178: if (!isset($this->_static[$name][1])) {
179: $callback = Helpers::checkCallback($this->_static[$name][0]);
180: if (is_string($callback) && strpos($callback, '::')) {
181: $callback = explode('::', $callback);
182: } elseif (is_object($callback)) {
183: $callback = [$callback, '__invoke'];
184: }
185: $ref = is_array($callback)
186: ? new \ReflectionMethod($callback[0], $callback[1])
187: : new \ReflectionFunction($callback);
188: $this->_static[$name][1] = ($tmp = $ref->getParameters())
189: && $tmp[0]->getClass() && $tmp[0]->getClass()->getName() === 'Latte\Runtime\FilterInfo';
190: }
191: return $this->_static[$name];
192: }
193: }
194: