/** * Parse mask and array of default values; initializes object. * @param string * @param array * @return void */ private function setMask($mask, array $metadata) { $this->mask = $mask; // detect '//host/path' vs. '/abs. path' vs. 'relative path' if (substr($mask, 0, 2) === '//') { $this->type = self::HOST; } elseif (substr($mask, 0, 1) === '/') { $this->type = self::PATH; } else { $this->type = self::RELATIVE; } foreach ($metadata as $name => $meta) { if (!is_array($meta)) { $metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT); } elseif (array_key_exists(self::VALUE, $meta)) { $metadata[$name]['fixity'] = self::CONSTANT; } } // PARSE MASK // <parameter-name[=default] [pattern] [#class]> or [ or ] or ?... $parts = NStrings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/'); $this->xlat = array(); $i = count($parts) - 1; // PARSE QUERY PART OF MASK if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') { // name=<parameter-name [pattern][#class]> $matches = NStrings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/'); foreach ($matches as $match) { list(, $param, $name, $pattern, $class) = $match; // $pattern is not used if ($class !== '') { if (!isset(self::$styles[$class])) { throw new InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set."); } $meta = self::$styles[$class]; } elseif (isset(self::$styles['?' . $name])) { $meta = self::$styles['?' . $name]; } else { $meta = self::$styles['?#']; } if (isset($metadata[$name])) { $meta = $metadata[$name] + $meta; } if (array_key_exists(self::VALUE, $meta)) { $meta['fixity'] = self::OPTIONAL; } unset($meta['pattern']); $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); $metadata[$name] = $meta; if ($param !== '') { $this->xlat[$name] = $param; } } $i -= 6; } // PARSE PATH PART OF MASK $brackets = 0; // optional level $re = ''; $sequence = array(); $autoOptional = array(0, 0); // strlen($re), count($sequence) do { array_unshift($sequence, $parts[$i]); $re = preg_quote($parts[$i], '#') . $re; if ($i === 0) { break; } $i--; $part = $parts[$i]; // [ or ] if ($part === '[' || $part === ']' || $part === '[!') { $brackets += $part[0] === '[' ? -1 : 1; if ($brackets < 0) { throw new InvalidArgumentException("Unexpected '$part' in mask '$mask'."); } array_unshift($sequence, $part); $re = ($part[0] === '[' ? '(?:' : ')?') . $re; $i -= 5; continue; } $class = $parts[$i]; $i--; // validation class $pattern = trim($parts[$i]); $i--; // validation condition (as regexp) $default = $parts[$i]; $i--; // default value $name = $parts[$i]; $i--; // parameter name array_unshift($sequence, $name); if ($name[0] === '?') { // "foo" parameter $re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re; $sequence[1] = substr($name, 1) . $sequence[1]; continue; } // check name (limitation by regexp) if (preg_match('#[^a-z0-9_-]#i', $name)) { throw new InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given."); } // pattern, condition & metadata if ($class !== '') { if (!isset(self::$styles[$class])) { throw new InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set."); } $meta = self::$styles[$class]; } elseif (isset(self::$styles[$name])) { $meta = self::$styles[$name]; } else { $meta = self::$styles['#']; } if (isset($metadata[$name])) { $meta = $metadata[$name] + $meta; } if ($pattern == '' && isset($meta[self::PATTERN])) { $pattern = $meta[self::PATTERN]; } if ($default !== '') { $meta[self::VALUE] = (string) substr($default, 1); $meta['fixity'] = self::PATH_OPTIONAL; } $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]); if (array_key_exists(self::VALUE, $meta)) { if (isset($meta['filterTable2'][$meta[self::VALUE]])) { $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]]; } elseif (isset($meta[self::FILTER_OUT])) { $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]); } else { $meta['defOut'] = $meta[self::VALUE]; } } $meta[self::PATTERN] = "#(?:$pattern)$#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); // include in expression $re = '(?P<' . str_replace('-', '___', $name) . '>(?U)' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name if ($brackets) { // is in brackets? if (!isset($meta[self::VALUE])) { $meta[self::VALUE] = $meta['defOut'] = NULL; } $meta['fixity'] = self::PATH_OPTIONAL; } elseif (isset($meta['fixity'])) { // auto-optional $re = '(?:' . substr_replace($re, ')?', strlen($re) - $autoOptional[0], 0); array_splice($sequence, count($sequence) - $autoOptional[1], 0, array(']', '')); array_unshift($sequence, '[', ''); $meta['fixity'] = self::PATH_OPTIONAL; } else { $autoOptional = array(strlen($re), count($sequence)); } $metadata[$name] = $meta; } while (TRUE); if ($brackets) { throw new InvalidArgumentException("Missing closing ']' in mask '$mask'."); } $this->re = '#' . $re . '/?$#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu'); $this->metadata = $metadata; $this->sequence = $sequence; }
/** * Returns position of token in input string. * @param int token number * @return array [offset, line, column] */ public function getOffset($i) { $tokens = NStrings::split($this->input, $this->re, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); $offset = isset($tokens[$i]) ? $tokens[$i][1] : strlen($this->input); return array( $offset, ($offset ? substr_count($this->input, "\n", 0, $offset) + 1 : 1), $offset - strrpos(substr($this->input, 0, $offset), "\n"), ); }
/** * Scan a directory for PHP files, subdirectories and 'netterobots.txt' file. * @param string * @return void */ private function scanDirectory($dir) { if (is_dir($dir)) { $ignoreDirs = is_array($this->ignoreDirs) ? $this->ignoreDirs : NStrings::split($this->ignoreDirs, '#[,\s]+#'); $disallow = array(); foreach ($ignoreDirs as $item) { if ($item = realpath($item)) { $disallow[$item] = TRUE; } } $iterator = NFinder::findFiles(is_array($this->acceptFiles) ? $this->acceptFiles : NStrings::split($this->acceptFiles, '#[,\s]+#')) ->filter(create_function('$file', 'extract(NCFix::$vars['.NCFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS); return !isset($disallow[$file->getPathname()]); ')) ->from($dir) ->exclude($ignoreDirs) ->filter($filter = create_function('$dir', 'extract(NCFix::$vars['.NCFix::uses(array('disallow'=>&$disallow)).'], EXTR_REFS); $path = $dir->getPathname(); if (is_file("$path/netterobots.txt")) { foreach (file("$path/netterobots.txt") as $s) { if ($matches = NStrings::match($s, \'#^disallow\\\\s*:\\\\s*(\\\\S+)#i\')) { $disallow[$path . str_replace(\'/\', DIRECTORY_SEPARATOR, rtrim(\'/\' . ltrim($matches[1], \'/\'), \'/\'))] = TRUE; } } } return !isset($disallow[$path]); ')); $filter(new SplFileInfo($dir)); } else { $iterator = new ArrayIterator(array(new SplFileInfo($dir))); } foreach ($iterator as $entry) { $path = $entry->getPathname(); if (!isset($this->files[$path]) || $this->files[$path] !== $entry->getMTime()) { $this->scanScript($path); } } }