/** * @param string presenter name * @return string class name * @throws InvalidPresenterException */ public function getPresenterClass(&$name) { if (isset($this->cache[$name])) { list($class, $name) = $this->cache[$name]; return $class; } if (!is_string($name) || !Strings::match($name, "#^[a-zA-Z-ÿ][a-zA-Z0-9-ÿ:]*\$#")) { throw new InvalidPresenterException("Presenter name must be alphanumeric string, '{$name}' is invalid."); } $class = $this->formatPresenterClass($name); if (!class_exists($class)) { // internal autoloading $file = $this->formatPresenterFile($name); if (is_file($file) && is_readable($file)) { LimitedScope::load($file, TRUE); } if (!class_exists($class)) { throw new InvalidPresenterException("Cannot load presenter '{$name}', class '{$class}' was not found in '{$file}'."); } } $reflection = new ClassReflection($class); $class = $reflection->getName(); if (!$reflection->implementsInterface('IPresenter')) { throw new InvalidPresenterException("Cannot load presenter '{$name}', class '{$class}' is not IPresenter implementor."); } if ($reflection->isAbstract()) { throw new InvalidPresenterException("Cannot load presenter '{$name}', class '{$class}' is abstract."); } // canonicalize presenter name $realName = $this->unformatPresenterClass($class); if ($name !== $realName) { if ($this->caseSensitive) { throw new InvalidPresenterException("Cannot load presenter '{$name}', case mismatch. Real name is '{$realName}'."); } else { $this->cache[$name] = array($class, $realName); $name = $realName; } } else { $this->cache[$name] = array($class, $realName); } return $class; }
/** * Tokenize string. * @param string * @return array */ public function tokenize($input) { $this->input = $input; if ($this->types) { $this->tokens = Strings::matchAll($input, $this->re); $len = 0; $count = count($this->types); $line = 1; foreach ($this->tokens as &$match) { $type = NULL; for ($i = 1; $i <= $count; $i++) { if (!isset($match[$i])) { break; } elseif ($match[$i] != NULL) { $type = $this->types[$i - 1]; break; } } $match = self::createToken($match[0], $type, $line); $len += strlen($match['value']); $line += substr_count($match['value'], "\n"); } if ($len !== strlen($input)) { $errorOffset = $len; } } else { $this->tokens = Strings::split($input, $this->re, PREG_SPLIT_NO_EMPTY); if ($this->tokens && !Strings::match(end($this->tokens), $this->re)) { $tmp = Strings::split($this->input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); list(, $errorOffset) = end($tmp); } } if (isset($errorOffset)) { $line = $errorOffset ? substr_count($this->input, "\n", 0, $errorOffset) + 1 : 1; $col = $errorOffset - strrpos(substr($this->input, 0, $errorOffset), "\n") + 1; $token = str_replace("\n", '\\n', substr($input, $errorOffset, 10)); throw new TokenizerException("Unexpected '{$token}' on line {$line}, column {$col}."); } return $this->tokens; }
/** * Parses macro tag to name, arguments a modifiers parts. * @param string {name arguments | modifiers} * @return array */ public function parseMacroTag($tag) { $match = Strings::match($tag, '~^ ( (?P<name>\\?|/?[a-z]\\w*+(?:[.:]\\w+)*+(?!::|\\())| ## ?, name, /name, but not function( or class:: (?P<noescape>!?)(?P<shortname>/?[=\\~#%^&_]?) ## !expression, !=expression, ... )(?P<args>.*?) (?P<modifiers>\\|[a-z](?:' . Parser::RE_STRING . '|[^\'"])*)? ()$~isx'); if (!$match) { return FALSE; } if ($match['name'] === '') { $match['name'] = ($tmp = $match['shortname']) ? $tmp : '='; if (!$match['noescape'] && substr($match['shortname'], 0, 1) !== '/') { $match['modifiers'] .= '|escape'; } } return array($match['name'], trim($match['args']), $match['modifiers']); }
/** * Parses phpDoc comment. * @param string * @return array */ private static function parseComment($comment) { static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE); $res = array(); $comment = preg_replace('#^\\s*\\*\\s?#ms', '', trim($comment, '/*')); $parts = preg_split('#^\\s*(?=@' . self::RE_IDENTIFIER . ')#m', $comment, 2); $description = trim($parts[0]); if ($description !== '') { $res['description'] = array($description); } $matches = XApp_Utils_Strings::matchAll(isset($parts[1]) ? $parts[1] : '', '~ (?<=\\s|^)@(' . self::RE_IDENTIFIER . ')[ \\t]* ## annotation ( \\((?>' . self::RE_STRING . '|[^\'")@]+)+\\)| ## (value) [^(@\\r\\n][^@\\r\\n]*|) ## value ~xi'); foreach ($matches as $match) { list(, $name, $value) = $match; if (substr($value, 0, 1) === '(') { $items = array(); $key = ''; $val = TRUE; $value[0] = ','; while ($m = Strings::match($value, '#\\s*,\\s*(?>(' . self::RE_IDENTIFIER . ')\\s*=\\s*)?(' . self::RE_STRING . '|[^\'"),\\s][^\'"),]*)#A')) { $value = substr($value, strlen($m[0])); list(, $key, $val) = $m; $val = rtrim($val); if ($val[0] === "'" || $val[0] === '"') { $val = substr($val, 1, -1); } elseif (is_numeric($val)) { $val = 1 * $val; } else { $lval = strtolower($val); $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val; } if ($key === '') { $items[] = $val; } else { $items[$key] = $val; } } $value = count($items) < 2 && $key === '' ? $val : $items; } else { $value = trim($value); if (is_numeric($value)) { $value = 1 * $value; } else { $lval = strtolower($value); $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value; } } $class = $name . 'Annotation'; if (class_exists($class)) { $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value)); } else { $res[$name][] = is_array($value) ? Nette\ArrayHash::from($value) : $value; } } return $res; }
/** * Maps HTTP request to a Request object. * @return PresenterRequest|NULL */ public function match(IHttpRequest $httpRequest) { // combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults // 1) URL MASK $url = $httpRequest->getUrl(); if ($this->type === self::HOST) { $path = '//' . $url->getHost() . $url->getPath(); } elseif ($this->type === self::RELATIVE) { $basePath = $url->getBasePath(); if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) { return NULL; } $path = (string) substr($url->getPath(), strlen($basePath)); } else { $path = $url->getPath(); } if ($path !== '') { $path = rtrim($path, '/') . '/'; } if (!($matches = Strings::match($path, $this->re))) { // stop, not matched return NULL; } // deletes numeric keys, restore '-' chars $params = array(); foreach ($matches as $k => $v) { if (is_string($k) && $v !== '') { $params[str_replace('___', '-', $k)] = $v; // trick } } // 2) CONSTANT FIXITY foreach ($this->metadata as $name => $meta) { if (isset($params[$name])) { //$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8 } elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) { $params[$name] = NULL; // cannot be overwriten in 3) and detected by isset() in 4) } } // 3) QUERY if ($this->xlat) { $params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat)); } else { $params += $httpRequest->getQuery(); } // 4) APPLY FILTERS & FIXITY foreach ($this->metadata as $name => $meta) { if (isset($params[$name])) { if (!is_scalar($params[$name])) { } elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters $params[$name] = $meta[self::FILTER_TABLE][$params[$name]]; } elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) { return NULL; // rejected by filterTable } elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters $params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]); if ($params[$name] === NULL && !isset($meta['fixity'])) { return NULL; // rejected by filter } } } elseif (isset($meta['fixity'])) { $params[$name] = $meta[self::VALUE]; } } // 5) BUILD Request if (!isset($params[self::PRESENTER_KEY])) { throw new InvalidStateException('Missing presenter in route definition.'); } if (isset($this->metadata[self::MODULE_KEY])) { if (!isset($params[self::MODULE_KEY])) { throw new InvalidStateException('Missing module in route definition.'); } $presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY]; unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]); } else { $presenter = $params[self::PRESENTER_KEY]; unset($params[self::PRESENTER_KEY]); } return new PresenterRequest($presenter, $httpRequest->getMethod(), $params, $httpRequest->getPost(), $httpRequest->getFiles(), array(PresenterRequest::SECURED => $httpRequest->isSecured())); }
/** * Parses PHP file. * @param string * @return void */ private static function parseScript($file) { $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE; $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR; $s = file_get_contents($file); if (Strings::match($s, '#//nette' . 'loader=(\\S*)#')) { return; // TODO: allways ignore? } $expected = $namespace = $class = $docComment = NULL; $level = $classLevel = 0; foreach (token_get_all($s) as $token) { if (is_array($token)) { switch ($token[0]) { case T_DOC_COMMENT: $docComment = $token[1]; case T_WHITESPACE: case T_COMMENT: continue 2; case T_STRING: case $T_NS_SEPARATOR: case T_VARIABLE: if ($expected) { $name .= $token[1]; } continue 2; case T_FUNCTION: case T_VAR: case T_PUBLIC: case T_PROTECTED: case $T_NAMESPACE: case T_CLASS: case T_INTERFACE: $expected = $token[0]; $name = NULL; continue 2; case T_STATIC: case T_ABSTRACT: case T_FINAL: continue 2; // ignore in expectation // ignore in expectation case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: $level++; } } if ($expected) { switch ($expected) { case T_CLASS: case T_INTERFACE: $class = $namespace . $name; $classLevel = $level; $name = ''; // break intentionally omitted // break intentionally omitted case T_FUNCTION: if ($token === '&') { continue 2; // ignore } case T_VAR: case T_PUBLIC: case T_PROTECTED: if ($class && $name !== NULL && $docComment) { self::$cache[$class][$name] = self::parseComment($docComment); } break; case $T_NAMESPACE: $namespace = $name . '\\'; } $expected = $docComment = NULL; } if ($token === ';') { $docComment = NULL; } elseif ($token === '{') { $docComment = NULL; $level++; } elseif ($token === '}') { $level--; if ($level === $classLevel) { $class = NULL; } } } }
/** * {control name[:method] [params]} */ public function macroControl(MacroNode $node, PhpWriter $writer) { $pair = $node->tokenizer->fetchWord(); if ($pair === FALSE) { throw new CompileException("Missing control name in {control}"); } $pair = explode(':', $pair, 2); $name = $writer->formatWord($pair[0]); $method = isset($pair[1]) ? ucfirst($pair[1]) : ''; $method = Strings::match($method, '#^\\w*$#') ? "render{$method}" : "{\"render{$method}\"}"; $param = $writer->formatArray(); if (!Strings::contains($node->args, '=>')) { $param = substr($param, 6, -1); // removes array() } return ($name[0] === '$' ? "if (is_object({$name})) \$_ctrl = {$name}; else " : '') . '$_ctrl = $_control->getComponent(' . $name . '); ' . 'if ($_ctrl instanceof IRenderable) $_ctrl->validateControl(); ' . "\$_ctrl->{$method}({$param})"; }
/** * Changes current action. Only alphanumeric characters are allowed. * @param string * @return void */ public function changeAction($action) { if (is_string($action) && Strings::match($action, '#^[a-zA-Z0-9][a-zA-Z0-9_\\x7f-\\xff]*$#')) { $this->action = $action; $this->view = $action; } else { $this->error('Action name is not alphanumeric string.'); } }
/** * Regular expression validator: matches control's value regular expression? * @param TextBase * @param string * @return bool */ public static function validatePattern(TextBase $control, $pattern) { return (bool) Strings::match($control->getValue(), "^({$pattern})\$u"); }
/** * Builds HTML content. * @return void */ protected function buildHtml() { if ($this->html instanceof ITemplate) { $this->html->mail = $this; if ($this->basePath === NULL && $this->html instanceof IFileTemplate) { $this->basePath = dirname($this->html->getFile()); } $this->html = $this->html->__toString(TRUE); } if ($this->basePath !== FALSE) { $cids = array(); $matches = Strings::matchAll($this->html, '#(src\\s*=\\s*|background\\s*=\\s*|url\\()(["\'])(?![a-z]+:|[/\\#])(.+?)\\2#i', PREG_OFFSET_CAPTURE); foreach (array_reverse($matches) as $m) { $file = rtrim($this->basePath, '/\\') . '/' . $m[3][0]; if (!isset($cids[$file])) { $cids[$file] = substr($this->addEmbeddedFile($file)->getHeader("Content-ID"), 1, -1); } $this->html = substr_replace($this->html, "{$m[1][0]}{$m[2][0]}cid:{$cids[$file]}{$m[2][0]}", $m[0][1], strlen($m[0][0])); } } if (!$this->getSubject() && ($matches = Strings::match($this->html, '#<title>(.+?)</title>#is'))) { $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')); } }