/** * @param string presenter name * @return string class name * @throws NInvalidPresenterException */ public function getPresenterClass(& $name) { if (isset($this->cache[$name])) { list($class, $name) = $this->cache[$name]; return $class; } if (!is_string($name) || !NStrings::match($name, "#^[a-zA-Z\x7f-\xff][a-zA-Z0-9\x7f-\xff:]*$#")) { throw new NInvalidPresenterException("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)) { NLimitedScope::load($file, TRUE); } if (!class_exists($class)) { throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' was not found in '$file'."); } } $reflection = new NClassReflection($class); $class = $reflection->getName(); if (!$reflection->implementsInterface('IPresenter')) { throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' is not IPresenter implementor."); } if ($reflection->isAbstract()) { throw new NInvalidPresenterException("Cannot load presenter '$name', class '$class' is abstract."); } // canonicalize presenter name $realName = $this->unformatPresenterClass($class); if ($name !== $realName) { if ($this->caseSensitive) { throw new NInvalidPresenterException("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; }
/** * Parses macro to name, arguments a modifiers parts. * @param string {name arguments | modifiers} * @return array */ public function parseMacro($macro) { $match = NStrings::match($macro, '~^ ( (?P<name>\?|/?[a-z]\w*+(?:[.:]\w+)*+(?!::|\())| ## ?, name, /name, but not function( or class:: (?P<noescape>!?)(?P<shortname>/?[=\~#%^&_]?) ## [!] [=] expression to print )(?P<args>.*?) (?P<modifiers>\|[a-z](?:'.NParser::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']); }
/** * Changes current action. Only alphanumeric characters are allowed. * @param string * @return void */ public function changeAction($action) { if (is_string($action) && NStrings::match($action, '#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*\z#')) { $this->action = $action; $this->view = $action; } else { $this->error('Action name is not alphanumeric string.'); } }
/** * Tokenize string. * @param string * @return array */ public function tokenize($input) { $this->input = $input; if ($this->types) { $this->tokens = NStrings::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 = NStrings::split($input, $this->re, PREG_SPLIT_NO_EMPTY); if ($this->tokens && !NStrings::match(end($this->tokens), $this->re)) { $tmp = NStrings::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 NTokenizerException("Unexpected '$token' on line $line, column $col."); } return $this->tokens; }
/** * Maps HTTP request to a Request object. * @param IHttpRequest * @return NPresenterRequest|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 = NStrings::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 NPresenterRequest( $presenter, $httpRequest->getMethod(), $params, $httpRequest->getPost(), $httpRequest->getFiles(), array(NPresenterRequest::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 (NStrings::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 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 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; } } } }
/** * Regular expression validator: matches control's value regular expression? * @param NTextBase * @param string * @return bool */ public static function validatePattern(NTextBase $control, $pattern) { return (bool) NStrings::match($control->getValue(), "\x01^($pattern)$\x01u"); }
/** * @return string */ public function getClassName() { return ($tmp = NStrings::match($this, '#>\s+([a-z0-9_\\\\]+)#i')) ? $tmp[1] : NULL; }
/** * {control name[:method] [params]} */ public function macroControl(NMacroNode $node, NPhpWriter $writer) { $pair = $node->tokenizer->fetchWord(); if ($pair === FALSE) { throw new NCompileException("Missing control name in {control}"); } $pair = explode(':', $pair, 2); $name = $writer->formatWord($pair[0]); $method = isset($pair[1]) ? ucfirst($pair[1]) : ''; $method = NStrings::match($method, '#^\w*\z#') ? "render$method" : "{\"render$method\"}"; $param = $writer->formatArray(); if (!NStrings::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)"; }
/** * Parses macro tag to name, arguments a modifiers parts. * @param string {name arguments | modifiers} * @return array */ public function parseMacroTag($tag) { $match = NStrings::match($tag, '~^ ( (?P<name>\?|/?[a-z]\w*+(?:[.:]\w+)*+(?!::|\(|\\\\))| ## ?, name, /name, but not function( or class:: or namespace\ (?P<noescape>!?)(?P<shortname>/?[=\~#%^&_]?) ## !expression, !=expression, ... )(?P<args>.*?) (?P<modifiers>\|[a-z](?:'.NParser::RE_STRING.'|[^\'"])*)? ()\z~isx'); if (!$match) { return FALSE; } $modifiers = preg_replace('#\|noescape\s?(?=\||\z)#i', '', $match['modifiers'], -1, $noescape); if ($match['name'] === '') { $match['name'] = ($tmp=$match['shortname']) ? $tmp : '='; if (!$noescape && !$match['noescape'] && substr($match['shortname'], 0, 1) !== '/') { $modifiers .= '|escape'; } } return array($match['name'], trim($match['args']), $modifiers); }
/** * Analyse PHP file. * @param string * @return void */ private function scanScript($file) { $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE; $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR; $T_TRAIT = PHP_VERSION_ID < 50400 ? -1 : T_TRAIT; $expected = FALSE; $namespace = ''; $level = $minLevel = 0; $time = filemtime($file); $s = file_get_contents($file); foreach ($this->list as $class => $pair) { if (is_array($pair) && $pair[0] === $file) { unset($this->list[$class]); } } if ($matches = NStrings::match($s, '#//nette'.'loader=(\S*)#')) { foreach (explode(',', $matches[1]) as $name) { $this->addClass($name, $file, $time); } return; } foreach (token_get_all($s) as $token) { if (is_array($token)) { switch ($token[0]) { case T_COMMENT: case T_DOC_COMMENT: case T_WHITESPACE: continue 2; case $T_NS_SEPARATOR: case T_STRING: if ($expected) { $name .= $token[1]; } continue 2; case $T_NAMESPACE: case T_CLASS: case T_INTERFACE: case $T_TRAIT: $expected = $token[0]; $name = ''; continue 2; case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: $level++; } } if ($expected) { switch ($expected) { case T_CLASS: case T_INTERFACE: case $T_TRAIT: if ($level === $minLevel) { $this->addClass($namespace . $name, $file, $time); } break; case $T_NAMESPACE: $namespace = $name ? $name . '\\' : ''; $minLevel = $token === '{' ? 1 : 0; } $expected = NULL; } if ($token === '{') { $level++; } elseif ($token === '}') { $level--; } } }
/** * Changes current action. Only alphanumeric characters are allowed. * @param string * @return void */ public function changeAction($action) { if (NStrings::match($action, "#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#")) { $this->action = $action; $this->view = $action; } else { throw new NBadRequestException("Action name '$action' is not alphanumeric string."); } }
/** * 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 = NStrings::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 = NStrings::match($this->html, '#<title>(.+?)</title>#is')) { $this->setSubject(html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8')); } }