From cd62bea3d34123887df32ede9b9556c1c1d18742 Mon Sep 17 00:00:00 2001 From: Guilherme Nascimento Date: Sun, 10 May 2020 06:39:41 -0300 Subject: [PATCH] Version 0.5.13 - Improved performance of `Inphinit\Experimental\Session` and `Inphinit\Packages` - `Inphinit\Helper::parseVersion()` use "Semantic Versioning 2.0.0" now - New param value for Request::is() method, eg: `Request::is('prefetch')` check if exists `Purpose` or `X-Moz` or `X-Purpose` headers, if exist check if value is `prefetch` (case-insenstive) - Improved performance of `Inphinit\Routing\Route` if is not using regex - Removed unnecessary check in `UtilsAutoload` function - Classes that are no longer experimental and are now available in the main namespace (can still be used with the "experimental" namespace to avoid breaking old projects): - `Inphinit\Experimental\Config` moved to `Inphinit\Config` - `Inphinit\Experimental\Debug` moved to `Inphinit\Debug` - `Inphinit\Experimental\Dir` moved to `Inphinit\Dir` - `Inphinit\Experimental\Dom\Document` moved to - `Inphinit\Experimental\Dom\DomException` moved to - `Inphinit\Experimental\Dom\Selector` moved to - `Inphinit\Experimental\Exception` moved to `Inphinit\Exception` - `Inphinit\Experimental\Maintenance` moved to `Inphinit\Maintenance` - Methods from `Inphinit\Experimental\File` class that are no longer experimental and are now available in `Inphinit\File` class (can still be used with the `Inphinit\Experimental\File` class to avoid breaking old projects): - `Inphinit\Experimental\File::lines()` moved to `Inphinit\File::lines()` - `Inphinit\Experimental\File::portion()` moved to `Inphinit\File::portion()` --- src/Experimental/Config.php | 199 +--------- src/Experimental/Debug.php | 325 +--------------- src/Experimental/Dir.php | 179 +-------- src/Experimental/Dom/Document.php | 510 +------------------------ src/Experimental/Dom/DomException.php | 28 +- src/Experimental/Dom/Selector.php | 246 +----------- src/Experimental/Exception.php | 21 +- src/Experimental/File.php | 26 +- src/Experimental/Maintenance.php | 61 +-- src/Experimental/Session.php | 12 +- src/Inphinit/App.php | 4 +- src/Inphinit/Config.php | 208 ++++++++++ src/Inphinit/Debug.php | 337 +++++++++++++++++ src/Inphinit/Dir.php | 188 +++++++++ src/Inphinit/Dom/Document.php | 525 ++++++++++++++++++++++++++ src/Inphinit/Dom/DomException.php | 40 ++ src/Inphinit/Dom/Selector.php | 258 +++++++++++++ src/Inphinit/Exception.php | 33 ++ src/Inphinit/File.php | 81 +++- src/Inphinit/Helper.php | 18 +- src/Inphinit/Http/Request.php | 7 +- src/Inphinit/Maintenance.php | 73 ++++ src/Inphinit/Packages.php | 7 +- src/Inphinit/Routing/Route.php | 7 +- src/Utils.php | 6 - 25 files changed, 1768 insertions(+), 1631 deletions(-) create mode 100644 src/Inphinit/Config.php create mode 100644 src/Inphinit/Debug.php create mode 100644 src/Inphinit/Dir.php create mode 100644 src/Inphinit/Dom/Document.php create mode 100644 src/Inphinit/Dom/DomException.php create mode 100644 src/Inphinit/Dom/Selector.php create mode 100644 src/Inphinit/Exception.php create mode 100644 src/Inphinit/Maintenance.php diff --git a/src/Experimental/Config.php b/src/Experimental/Config.php index acf6e6a..a5b5a61 100644 --- a/src/Experimental/Config.php +++ b/src/Experimental/Config.php @@ -9,203 +9,6 @@ namespace Inphinit\Experimental; -use Inphinit\Helper; -use Inphinit\Storage; - -class Config implements \IteratorAggregate +class Config extends \Inphinit\Config { - private static $exceptionlevel = 3; - private $data = array(); - private $path; - - /** - * Return items from a config file in a object (iterator or with ->) - * - * @param string $path - * @throws \Inphinit\Experimental\Exception - * @return void - */ - public function __construct($path) - { - $this->path = 'application/Config/' . strtr($path, '.', '/') . '.php'; - - $this->reload(); - } - - /** - * Create a Negotiation instance - * - * @param string $path - * @throws \Inphinit\Experimental\Exception - * @return \Inphinit\Experimental\Config - */ - public static function load($path) - { - self::$exceptionlevel = 4; - - return new static($path); - } - - /** - * Reload configuration from file - * - * @param string $path - * @throws \Inphinit\Experimental\Exception - * @return \Inphinit\Experimental\Config - */ - public function reload() - { - $level = self::$exceptionlevel; - - self::$exceptionlevel = 2; - - if (false === \Inphinit\File::exists(INPHINIT_PATH . $this->path)) { - throw new Exception('File not found ' . $this->path, $level); - } - - foreach (\UtilsSandboxLoader($this->path) as $key => $value) { - $this->data[$key] = $value; - } - - return $this; - } - - /** - * Reload configuration from file - * - * @return bool - */ - public function save() - { - if (Storage::createFolder('tmp/cfg')) { - $wd = preg_replace('#,(\s+|)\)#', '$1)', var_export($this->data, true)); - $path = Storage::temp('path); - - unlink($path); - - return $response; - } - } - - return false; - } - - /** - * Get all values like array or get specific item by level (multidimensional) using path - * - * @param string $path (optional) Path with "dots" - * @param string $alternative (optional) alternative value does not find the selected value, default is null - * @return mixed - */ - public function get($path = null, $alternative = null) - { - if ($path === null) { - return $this->data; - } - - return Helper::extract($path, $this->data, $alternative); - } - - /** - * Set value by path in specific level (multidimensional) - * - * @param string $path Path with "dots" - * @param mixed $value Define value - * @return \Inphinit\Experimental\Config - */ - public function set($path, $value) - { - $paths = explode('.', $path); - - $key = array_shift($paths); - - $tree = $value; - - foreach (array_reverse($paths) as $item) { - $tree = array($item => $tree); - } - - $this->data[$key] = $tree; - - $tree = null; - - return $this; - } - - /** - * Magic method for get specific item by -> - * - * @param string $name - * @return mixed - */ - public function __get($name) - { - if (array_key_exists($name, $this->data)) { - return $this->data[$name]; - } - } - - /** - * Magic method for set value (this method don't save data) - * - * @param string $name - * @param mixed $value - * @return void - */ - public function __set($name, $value) - { - $this->data[$name] = $value; - } - - /** - * Magic method for check if value exists in top-level - * - * @param string $name - * @return bool - */ - public function __isset($name) - { - return array_key_exists($name, $this->data); - } - - /** - * Magic method for unset variable with `unset()` function - * - * @param string $name - * @return void - */ - public function __unset($name) - { - unset($this->data[$name]); - } - - /** - * Allow iteration with `for`, `foreach` and `while` - * - * Example: - *
-     * 
-     * $foo = new Config('file'); //or Config::load('file')
-     *
-     * foreach ($foo as $key => $value) {
-     *     var_dump($key, $value);
-     *     echo EOL;
-     * }
-     * 
-     * 
- * - * @return \ArrayIterator - */ - public function getIterator() - { - return new \ArrayIterator($this->data); - } - - public function __destruct() - { - $this->data = null; - } } diff --git a/src/Experimental/Debug.php b/src/Experimental/Debug.php index b449083..73a9164 100644 --- a/src/Experimental/Debug.php +++ b/src/Experimental/Debug.php @@ -9,329 +9,6 @@ namespace Inphinit\Experimental; -use Inphinit\App; -use Inphinit\Http\Request; -use Inphinit\Http\Response; -use Inphinit\Viewing\View; -use Inphinit\Experimental\Config; - -class Debug +class Debug extends \Inphinit\Debug { - private static $showBeforeView = false; - private static $linkSearchError; - private static $displayErrors; - private static $views = array(); - private static $fatal = array(E_ERROR, E_PARSE, E_COMPILE_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR); - - /** - * Unregister debug events - * - * @return void - */ - public static function unregister() - { - $nc = '\\' . get_called_class(); - - App::off('error', array( $nc, 'renderError' )); - App::off('terminate', array( $nc, 'renderPerformance' )); - App::off('terminate', array( $nc, 'renderClasses' )); - - if (false === empty(self::$displayErrors)) { - if (function_exists('init_set')) { - ini_set('display_errors', self::$displayErrors); - } - - self::$displayErrors = null; - } - } - - /** - * Render a View to error - * - * @param int $type - * @param string $message - * @param string $file - * @param int $line - * @return void - */ - public static function renderError($type, $message, $file, $line) - { - if (empty(self::$views['error'])) { - return null; - } elseif (preg_match('#allowed\s+memory\s+size\s+of\s+\d+\s+bytes\s+exhausted\s+\(tried\s+to\s+allocate\s+\d+\s+bytes\)#i', $message)) { - die("
Fatal error: $message in $file on line $line"); - } - - $data = self::details($type, $message, $file, $line); - - if (!headers_sent() && strpos(Request::header('accept'), 'application/json') === 0) { - ob_start(); - - self::unregister(); - - Response::cache(0); - Response::type('application/json'); - - echo json_encode($data); - - App::stop(500); - } - - if (in_array($type, self::$fatal)) { - View::dispatch(); - } - - self::render(self::$views['error'], $data); - } - - /** - * Render a View to show performance, memory and time to display page - * - * @return void - */ - public static function renderPerformance() - { - if (isset(self::$views['performance'])) { - self::render(self::$views['performance'], self::performance()); - } - } - - /** - * Render a View to show performance and show declared classes - * - * @return void - */ - public static function renderClasses() - { - if (isset(self::$views['classes'])) { - self::render(self::$views['classes'], array( - 'classes' => self::classes() - )); - } - } - - /** - * Register a debug views - * - * @param string $type - * @param string $view - * @throws \Inphinit\Experimental\Exception - * @return void - */ - public static function view($type, $view) - { - if ($view !== null && View::exists($view) === false) { - throw new Exception($view . ' view is not found', 2); - } - - $callRender = array( '\\' . get_called_class(), 'render' . ucfirst($type) ); - - if ($type === 'error') { - App::on('error', $callRender); - - if (empty(self::$displayErrors)) { - self::$displayErrors = ini_get('display_errors'); - - if (function_exists('ini_set')) { - ini_set('display_errors', '0'); - } - } - } elseif ($type === 'classes' || $type === 'performance') { - App::on('terminate', $callRender); - } elseif ($type !== 'before') { - throw new Exception($type . ' is not valid event', 2); - } - - self::$views[$type] = $view; - } - - /** - * Get memory usage and you can also use it to calculate runtime. - * - * @return array - */ - public static function performance() - { - return array( - 'usage' => memory_get_usage() / 1024, - 'peak' => memory_get_peak_usage() / 1024, - 'real' => memory_get_peak_usage(true) / 1024, - 'time' => microtime(true) - INPHINIT_START - ); - } - - /** - * Get declared classes - * @return array - */ - public static function classes() - { - $objs = array(); - - foreach (get_declared_classes() as $value) { - $value = ltrim($value, '\\'); - $cname = new \ReflectionClass($value); - - if (false === $cname->isInternal()) { - $objs[$value] = $cname->getDefaultProperties(); - } - } - - $cname = null; - - return $objs; - } - - /** - * Get snippet from a file - * - * @param string $file - * @param int $line - * @return array|null - */ - public static function source($file, $line) - { - if ($line <= 0 || is_file($file) === false) { - return null; - } elseif ($line > 5) { - $init = $line - 6; - $max = 10; - $breakpoint = 6; - } else { - $init = 0; - $max = 5; - $breakpoint = $line; - } - - $preview = preg_split('#\r\n|\n#', File::lines($file, $init, $max)); - - if (count($preview) !== $breakpoint && trim(end($preview)) === '') { - array_pop($preview); - } - - return array( - 'breakpoint' => $breakpoint, - 'preview' => $preview - ); - } - - /** - * Get backtrace php scripts - * - * @param int $level - * @return array|null - */ - public static function caller($level = 0) - { - $trace = debug_backtrace(0); - - foreach ($trace as $key => &$value) { - if (isset($value['file'])) { - self::evalFileLocation($value['file'], $value['line']); - } else { - unset($trace[$key]); - } - } - - $trace = array_values($trace); - - if ($level < 0) { - return $trace; - } elseif (isset($trace[$level])) { - return $trace = $trace[$level]; - } - } - - /** - * Convert error message in a link, see `system/config/debug.php` - * - * @param string $message - * @return string - */ - public static function searcherror($message) - { - if (self::$linkSearchError === null) { - self::$linkSearchError = Config::load('debug')->get('searcherror'); - } - - $link = self::$linkSearchError; - - if (strpos($link, '%error%') === -1) { - return $message; - } - - return preg_replace_callback('#^([\s\S]+?)\s+in\s+([\s\S]+?:\d+)|^([\s\S]+?)$#', function ($matches) use ($link) { - $error = empty($matches[3]) ? $matches[1] : $matches[3]; - - $url = str_replace('%error%', urlencode($error), $link); - $url = htmlentities($url); - - return '' . $error . '' . - (empty($matches[2]) ? '' : (' in ' . $matches[2])); - }, $message); - } - - private static function render($view, $data) - { - if (!self::$showBeforeView && isset(self::$views['before'])) { - self::$showBeforeView = true; - View::render(self::$views['before']); - } - - View::render($view, $data); - } - - private static function details($type, $message, $file, $line) - { - $match = array(); - - if (preg_match('#called in ([\s\S]+?) on line (\d+)#', $message, $match)) { - $file = $match[1]; - $line = (int) $match[2]; - } - - self::evalFileLocation($file, $line); - - switch ($type) { - case E_PARSE: - $message = 'Parse error: ' . $message; - break; - - case E_DEPRECATED: - case E_USER_DEPRECATED: - $message = 'Deprecated: ' . $message; - break; - - case E_ERROR: - case E_USER_ERROR: - case E_RECOVERABLE_ERROR: - $message = 'Fatal error: ' . $message; - break; - - case E_WARNING: - case E_USER_WARNING: - $message = 'Warning: ' . $message; - break; - - case E_NOTICE: - case E_USER_NOTICE: - $message = 'Notice: ' . $message; - break; - } - - return array( - 'message' => $message, - 'file' => $file, - 'line' => $line, - 'source' => $line > -1 ? self::source($file, $line) : null - ); - } - - private static function evalFileLocation(&$file, &$line) - { - if (preg_match('#(.*?)\((\d+)\) : eval\(\)\'d code#', $file, $match)) { - $file = $match[1]; - $line = (int) $match[2]; - } - } } diff --git a/src/Experimental/Dir.php b/src/Experimental/Dir.php index 30a86e3..d54de7d 100644 --- a/src/Experimental/Dir.php +++ b/src/Experimental/Dir.php @@ -9,183 +9,6 @@ namespace Inphinit\Experimental; -class Dir implements \Iterator, \Countable +class Dir extends \Inphinit\Dir { - private $position = 0; - private $item = false; - private $path = ''; - private $handle; - private $size = -1; - - /** - * Return items from a folder - * - * @param string $path - * @throws \Inphinit\Experimental\Exception - * @return void - */ - public function __construct($path) - { - if (is_dir($path) === false) { - throw new Exception('Folder not found', 2); - } - - $this->handle = opendir($path); - - if ($this->handle === false) { - throw new Exception('Failed to open folder', 2); - } - - $path = strtr(realpath($path), '\\', '/'); - - $this->path = rtrim($path, '/') . '/'; - - $this->find(0); - } - - /** - * Return items from root project folder (probably, will depend on the setting - * of the `INPHINIT_ROOT` constant) - * - * @return \Inphinit\Experimental\Dir - */ - public static function root() - { - return new static(INPHINIT_ROOT); - } - - /** - * Return items from storage folder - * - * @return \Inphinit\Experimental\Dir - */ - public static function storage() - { - return new static(INPHINIT_PATH . 'storage'); - } - - /** - * Return items from application folder - * - * @return \Inphinit\Experimental\Dir - */ - public static function application() - { - return new static(INPHINIT_PATH . 'application'); - } - - /** - * Resets the directory stream to the beginning of the directory - * - * @return void - */ - public function rewind() - { - $this->position = $this->find(0); - } - - /** - * Get current file with type, path and filename - * The entries are returned in the order in which they are stored by the filesystem. - * - * @return stdClass|null - */ - public function current() - { - if ($this->item !== false) { - $current = $this->path . $this->item; - - return (object) array( - 'type' => filetype($current), - 'path' => $current, - 'name' => $this->item, - 'position' => $this->position - ); - } - } - - /** - * Get current position in handle - * - * @return int - */ - public function key() - { - return $this->position; - } - - /** - * Move forward to next file - * - * @return void - */ - public function next() - { - $this->item = readdir($this->handle); - - if ($this->item !== false) { - ++$this->position; - } - } - - /** - * Check if pointer is valid - * - * @return bool - */ - public function valid() - { - return $this->item !== false; - } - - /** - * Count files in folder, can br used by `count($instance)` funciton - * - * @return int - */ - public function count() - { - if ($this->size === -1) { - $this->size = $this->find(-1); - - //Restore position - if ($this->position > 0) { - $this->find($this->position); - } - } - - return $this->size; - } - - private function find($pos) - { - rewinddir($this->handle); - - $current = 0; - $break = $pos !== -1; - - while (false !== ($item = readdir($this->handle))) { - if ($item === '.' || $item === '..') { - continue; - } elseif ($current === $pos) { - $this->item = $item; - - if ($break) { - break; - } - } - - ++$current; - } - - return $current !== $pos ? $current : 0; - } - - public function __destruct() - { - if ($this->handle) { - closedir($this->handle); - $this->handle = null; - } - } } diff --git a/src/Experimental/Dom/Document.php b/src/Experimental/Dom/Document.php index 6694688..a4c6c83 100644 --- a/src/Experimental/Dom/Document.php +++ b/src/Experimental/Dom/Document.php @@ -12,514 +12,6 @@ use Inphinit\Helper; use Inphinit\Storage; -class Document extends \DOMDocument +class Document extends \Inphinit\Dom\Document { - private $xpath; - private $selector; - - private $internalErr; - private $exceptionlevel = 3; - - private $complete = false; - private $simple = false; - - /** - * Used with `Document::reporting` method or in extended classes - * - * @var array - */ - protected $levels = array(\LIBXML_ERR_WARNING, \LIBXML_ERR_ERROR, \LIBXML_ERR_FATAL); - - /** Used with `Document::save` method to save document in XML format */ - const XML = 1; - - /** Used with `Document::save` method to save document in HTML format */ - const HTML = 2; - - /** Used with `Document::save` method to convert and save document in JSON format */ - const JSON = 3; - - /** Used with `Document::toArray` method to convert document in a simple array */ - const SIMPLE = 4; - - /** Used with `Document::toArray` method to convert document in a minimal array */ - const MINIMAL = 5; - - /** Used with `Document::toArray` method to convert document in a array with all properties */ - const COMPLETE = 6; - - /** - * Create a Document instance - * - * @param string $version The version number of the document as part of the XML declaration - * @param string $encoding The encoding of the document as part of the XML declaration - * @return void - */ - public function __construct($version = '1.0', $encoding = 'UTF-8') - { - parent::__construct($version, $encoding); - } - - /** - * Set level error for exception, set `LIBXML_ERR_NONE` (or `0` - zero) for disable exceptions. - * For disable only warnings use like this `$dom->reporting(LIBXML_ERR_FATAL, LIBXML_ERR_ERROR)` - * - * - * - * @param int $args,... - * @return void - */ - public function reporting() - { - $this->levels = func_get_args(); - } - - /** - * Convert a array in node elements - * - * @param array|\Traversable $data - * @throws \Inphinit\Experimental\Dom\DomException - * @return void - */ - public function fromArray(array $data) - { - if (empty($data)) { - throw new DomException('Array is empty', 2); - } elseif (count($data) > 1) { - throw new DomException('Root array accepts only a key', 2); - } - - $root = key($data); - - if (self::validTag($root) === false) { - throw new DomException('Invalid root <' . $root . '> tag', 2); - } - - if ($this->documentElement) { - $this->removeChild($this->documentElement); - } - - $this->enableRestoreInternal(true); - - $this->generate($this, $data, 2); - - $this->raise($this->exceptionlevel); - - $this->enableRestoreInternal(false); - } - - /** - * Convert DOM to JSON string - * - * @param bool $format - * @param int $options `JSON_HEX_QUOT`, `JSON_HEX_TAG`, `JSON_HEX_AMP`, `JSON_HEX_APOS`, `JSON_NUMERIC_CHECK`, `JSON_PRETTY_PRINT`, `JSON_UNESCAPED_SLASHES`, `JSON_FORCE_OBJECT`, `JSON_PRESERVE_ZERO_FRACTION`, `JSON_UNESCAPED_UNICODE`, `JSON_PARTIAL_OUTPUT_ON_ERROR`. The behaviour of these constants is described in http://php.net/manual/en/json.constants.php - * - * @return string - */ - public function toJson($format = Document::MINIMAL, $options = 0) - { - $this->exceptionlevel = 4; - - $json = json_encode($this->toArray($format), $options); - - $this->exceptionlevel = 3; - - return $json; - } - - /** - * Convert DOM to Array - * - * @param int $type - * @throws \Inphinit\Experimental\Dom\DomException - * @return array - */ - public function toArray($type = Document::SIMPLE) - { - switch ($type) { - case Document::MINIMAL: - $this->simple = false; - $this->complete = false; - break; - - case Document::SIMPLE: - $this->simple = true; - break; - - case Document::COMPLETE: - $this->complete = true; - break; - - default: - throw new DomException('Invalid type', 2); - } - - return $this->getNodes($this->childNodes); - } - - /** - * Magic method, return a well-formed XML string - * - * Example: - *
-     * 
-     * $xml = new Dom;
-     *
-     * $xml->fromArray(array(
-     *     'foo' => 'bar'
-     * ));
-     *
-     * echo $xml;
-     * 
-     * 
- * - * @return string - */ - public function __toString() - { - return $this->saveXML(); - } - - /** - * Save file to location - * - * @param string $path - * @param int $format Support XML, HTML, and JSON - * @throws \Inphinit\Experimental\Dom\DomException - * @return void - */ - public function save($path, $format = Document::XML) - { - switch ($format) { - case Document::XML: - $format = 'saveXML'; - break; - case Document::HTML: - $format = 'saveHTML'; - break; - case Document::JSON: - $format = 'toJson'; - break; - default: - throw new DomException('Invalid format', 2); - } - - if (Storage::createFolder('tmp/dom')) { - $tmp = Storage::temp($this->$format(), 'tmp/dom'); - } else { - $tmp = false; - } - - if ($tmp === false) { - throw new DomException('Can\'t create tmp file', 2); - } elseif (copy($tmp, $path) === false) { - throw new DomException('Can\'t copy tmp file to ' . $path, 2); - } else { - unlink($tmp); - } - } - - /** - * Get namespace attributes from root element or specific element - * - * @param \DOMElement $element - * @return void - */ - public function getNamespaces(\DOMElement $element = null) - { - if ($this->xpath === null) { - $this->xpath = new \DOMXPath($this); - } - - if ($element === null) { - $nodes = $this->xpath->query('namespace::*'); - $element = $this->documentElement; - } else { - $nodes = $this->xpath->query('namespace::*', $element); - } - - $ns = array(); - - if ($nodes) { - foreach ($nodes as $node) { - $arr = $element->getAttribute($node->nodeName); - - if ($arr) { - $ns[$node->nodeName] = $arr; - } - } - - $nodes = null; - } - - return $ns; - } - - /** - * Load XML from a string - * - * @param string $source - * @param int $options - * @throws \Inphinit\Experimental\Dom\DomException - * @return mixed - */ - public function loadXML($source, $options = 0) - { - return $this->resource('loadXML', $source, $options); - } - - /** - * Load XML from a file - * - * @param string $filename - * @param int $options - * @throws \Inphinit\Experimental\Dom\DomException - * @return mixed - */ - public function load($filename, $options = 0) - { - return $this->resource('load', $filename, $options); - } - - /** - * Load HTML from a string - * - * @param string $source - * @param int $options - * @throws \Inphinit\Experimental\Dom\DomException - * @return mixed - */ - public function loadHTML($source, $options = 0) - { - return $this->resource('loadHTML', $source, $options); - } - - /** - * Load HTML from a file - * - * @param string $filename - * @param int $options - * @throws \Inphinit\Experimental\Dom\DomException - * @return mixed - */ - public function loadHTMLFile($filename, $options = 0) - { - return $this->resource('loadHTMLFile', $filename, $options); - } - - /** - * Use query-selector like CSS, jQuery, querySelectorAll - * - * @param string $selector - * @param \DOMNode $context - * @return \DOMNodeList - */ - public function query($selector, \DOMNode $context = null) - { - $this->enableRestoreInternal(true); - - if ($this->selector === null) { - $this->selector = new Selector($this); - } - - $nodes = $this->selector->get($selector, $context); - - $level = $this->exceptionlevel; - - $this->exceptionlevel = 3; - - $this->raise($level); - - $this->enableRestoreInternal(false); - - return $nodes; - } - - /** - * Use query-selector like CSS, jQuery, querySelector - * - * @param string $selector - * @param \DOMNode $context - * @return \DOMNode - */ - public function first($selector, \DOMNode $context = null) - { - $this->exceptionlevel = 4; - - $nodes = $this->query($selector, $context); - - $node = $nodes ? $nodes->item(0) : null; - - $nodes = null; - - return $node; - } - - private function resource($function, $from, $options) - { - $this->enableRestoreInternal(true); - - $resource = PHP_VERSION_ID >= 50400 ? parent::$function($from, $options) : parent::$function($from); - - $this->raise(4); - - $this->enableRestoreInternal(false); - - return $resource; - } - - private function enableRestoreInternal($enable) - { - \libxml_clear_errors(); - - if ($enable) { - $this->internalErr = \libxml_use_internal_errors(true); - } else { - \libxml_use_internal_errors($this->internalErr); - } - } - - private function raise($debuglvl) - { - $err = \libxml_get_errors(); - - if (isset($err[0]->level) && in_array($err[0]->level, $this->levels, true)) { - throw new DomException(null, $debuglvl); - } - - \libxml_clear_errors(); - } - - private function generate(\DOMNode $node, $data, $errorLevel) - { - if (is_array($data) === false) { - $node->textContent = $data; - return; - } - - $nextLevel = $errorLevel + 1; - - foreach ($data as $key => $value) { - if ($key === '@comments') { - continue; - } elseif ($key === '@contents') { - $this->generate($node, $value, $nextLevel); - } elseif ($key === '@attributes') { - self::setAttributes($node, $value); - } elseif (self::validTag($key)) { - if (Helper::seq($value)) { - foreach ($value as $subvalue) { - $this->generate($node, array($key => $subvalue), $nextLevel); - } - } elseif (is_array($value)) { - $this->generate($this->add($key, '', $node), $value, $nextLevel); - } else { - $this->add($key, $value, $node); - } - } else { - throw new DomException('Invalid tag: <' . $key . '>', $nextLevel); - } - } - } - - private static function validTag($tagName) - { - return preg_match('#^([a-z_](\w+|)|[a-z_](\w+|):[a-z_](\w+|))$#i', $tagName) > 0; - } - - private function add($name, $value, \DOMNode $node) - { - $newdom = $this->createElement($name, $value); - $node->appendChild($newdom); - return $newdom; - } - - private static function setAttributes(\DOMNode $node, array &$attributes) - { - foreach ($attributes as $name => $value) { - $node->setAttribute($name, $value); - } - } - - private function getNodes($nodes) - { - $items = array(); - - if ($nodes) { - foreach ($nodes as $node) { - if ($node->nodeType === \XML_ELEMENT_NODE && ($this->complete || $this->simple || ctype_alnum($node->nodeName))) { - $items[$node->nodeName][] = $this->nodeContents($node); - } - } - - if (empty($items) === false) { - self::simplify($items); - } - } - - return $items; - } - - private function nodeContents(\DOMElement $node) - { - $extras = array( '@attributes' => array() ); - - if ($this->complete) { - foreach ($node->attributes as $attribute) { - $extras['@attributes'][$attribute->nodeName] = $attribute->nodeValue; - } - } - - if ($this->complete && ($ns = $this->getNamespaces($node))) { - $extras['@attributes'] = $extras['@attributes'] + $ns; - } - - if ($node->getElementsByTagName('*')->length) { - $r = $this->getNodes($node->childNodes) + ( - empty($extras['@attributes']) ? array() : $extras - ); - } elseif (empty($extras['@attributes'])) { - return $node->nodeValue; - } else { - $r = array($node->nodeValue) + $extras; - } - - self::simplify($r); - - return $r; - } - - private static function simplify(&$items) - { - if (self::toContents($items)) { - foreach ($items as $name => &$item) { - if (is_array($item) === false || strpos($name, '@') !== false) { - continue; - } - - if (count($item) === 1 && isset($item[0])) { - $item = $item[0]; - } else { - self::toContents($item); - } - } - } - } - - private static function toContents(&$item) - { - if (count($item) > 1 && isset($item[0]) && isset($item[1]) === false) { - $item['@contents'] = $item[0]; - unset($item[0]); - - return false; - } - - return true; - } } diff --git a/src/Experimental/Dom/DomException.php b/src/Experimental/Dom/DomException.php index 3236185..1758f48 100644 --- a/src/Experimental/Dom/DomException.php +++ b/src/Experimental/Dom/DomException.php @@ -9,32 +9,6 @@ namespace Inphinit\Experimental\Dom; -class DomException extends \Inphinit\Experimental\Exception +class DomException extends \Inphinit\Dom\DomException { - /** - * Raise an exception - * - * @param string $message - * @param int $trace - * @return void - */ - public function __construct($message = null, $trace = 1) - { - $err = \libxml_get_errors(); - - ++$trace; - - if ($message === null && isset($err[0]->message)) { - $message = trim($err[0]->message); - } - - if (isset($err[0]->file) && $err[0]->line > 0) { - $this->file = preg_replace('#^file:/(\w+:)#i', '$1', $err[0]->file); - $this->line = $err[0]->line; - $this->code = $err[0]->code; - $trace = 0; - } - - parent::__construct($message, $trace); - } } diff --git a/src/Experimental/Dom/Selector.php b/src/Experimental/Dom/Selector.php index 3f7b21e..9fef89a 100644 --- a/src/Experimental/Dom/Selector.php +++ b/src/Experimental/Dom/Selector.php @@ -9,250 +9,6 @@ namespace Inphinit\Experimental\Dom; -class Selector extends \DOMXPath +class Selector extends \Inphinit\Dom\Selector { - private $prevent; - private $rules; - private $allSiblingsToken; - private static $cache = array(); - private $qxs = array( - array( '/^([^a-z*])/i', '*\\1' ), - array( '/([>+~])([^\w*\s])/', '\\1*\\2' ), - array( '/\[(.*?)\]/', '[@\\1\\2]' ), - array( '/\.(\w+)/', '[@class~="\\1" i]' ), - array( '/#(\w+)/', '[@id="\\1" i]' ), - array( '/\:lang\(([\w\-]+)\)/i', '[@lang|="\\1" i]' ), - array( '/([>+~]|^)\:nth-(last-)?(child|of-type)\(n\)/i', '\\1*' ), - array( '/\:nth-(last-)?(child|of-type)\(n\)/i', '' ), - array( '/\:first-child/', ':nth-child(1)'), - array( '/\:nth-child\(odd\)/i', ':nth-child(2n+1)' ), - array( '/\:nth-child\(even\)/i', ':nth-child(2n)' ), - array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)\)/i', '\\1*[position()=\\3]\\2' ), - array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)n\)/i', '\\1*[(position() mod \\3=0)]\\2' ), - array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)n\+(\d+)\)/i', '\\1*[(position() mod \\3=\\4)]\\2' ), - array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)\)/i', '\\1*[name()="\\2" and position()=\\4]\\3' ), - array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)n\)/i', '\\1*[name()="\\2" and (position() mod \\4=0)]\\3' ), - array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)n\+(\d+)\)/i', '\\1*[name()="\\2" and (position() mod \\4=\\5)]\\3' ), - array( '/([>+~])?\*([^>+~]+)?\:only-child/i', '\\1*[last()=1]\\2' ), - array( '/([>+~])?\*([^>+~]+)?\:last-child/i', '\\1*[position()=last()]\\2' ), - array( '/([>+~])?(\w+)([^>+~]+)?\:only-child/i', '\\1*[name()="\\2" and last()=1]\\3' ), - array( '/([>+~])?(\w+)([^>+~]+)?\:last-child/i', '\\1*[name()="\\2" and position()=last()]\\3' ), - array( '/\:empty/i', '[not(text())]'), - array( '/\[(@\w+)(.)?=(.*?) i]/', '[lower-case(\\1)\\2=lower-case(\\3)]' ), - array( '/\[(@\w+|lower-case\(@\w+\))\*=(.*?)\]/', '[contains(\\1,\\2)]' ), - array( '/\[(@\w+|lower-case\(@\w+\))\^=(.*?)\]/', '[starts-with(\\1,\\2)]' ), - array( '/\[(@\w+|lower-case\(@\w+\))\~=(.*?)\]/', '[contains(concat(" ",\\1," "),concat(" ",\\2," "))]' ), - array( '/\[(@\w+|lower-case\(@\w+\))\|=(.*?)\]/', '[starts-with(concat(\\1,"-"),concat(\\2,"-"))]' ), - array( '/\[(@\w+|lower-case\(@\w+\))\$=(.*?)\]/', '[substring(\\1,string-length(\\1)-2)=\\2]' ), - array( '/\:contains\((.*?)\)/i', '[contains(.,\\1)]' ), - array( '/\:contains-child\((.*?)\)/i', '[text()[contains(.,\\1)]]' ) - ); - - /** - * Count all nodes matching the given CSS selector - * - * @param string $selector - * @param \DOMNode $context - * @param bool $registerNodeNS - * @return \DOMNodeList - */ - public function count($selector, \DOMNode $context = null, $registerNodeNS = true) - { - return $this->exec('evaluate', $selector, $context, $registerNodeNS); - } - - /** - * Returns a \DOMNodeList containing all nodes matching the given CSS selector - * - * @param string $selector - * @param \DOMNode $context - * @param bool $registerNodeNS - * @return \DOMNodeList - */ - public function get($selector, \DOMNode $context = null, $registerNodeNS = true) - { - return $this->exec('query', $selector, $context, $registerNodeNS); - } - - private function exec($method, $query, $context, $registerNodeNS) - { - $query = $this->toXPath($query); - - if (PHP_VERSION_ID >= 50303) { - return $this->$method($query, $context, $registerNodeNS); - } elseif ($context !== null) { - return $this->$method($query, $context); - } - - return $this->$method($query); - } - - private function tokens($query) - { - $dot = self::uniqueToken($query, 'dot'); - $hash = self::uniqueToken($query, 'hash'); - $spaces = self::uniqueToken($query, 'space'); - $commas = self::uniqueToken($query, 'comma'); - $child = self::uniqueToken($query, 'child'); - $sibling = self::uniqueToken($query, 'sibling'); - $adjacent = self::uniqueToken($query, 'adjacent'); - $lbracket = self::uniqueToken($query, 'lbracket'); - $rbracket = self::uniqueToken($query, 'rbracket'); - $lparenthesis = self::uniqueToken($query, 'lparenthesis'); - $rparenthesis = self::uniqueToken($query, 'rparenthesis'); - $lowercasefunction = self::uniqueToken($query, 'lowercase'); - - $this->prevent = array( - '.' => $dot, - '#' => $hash, - ' ' => $spaces, - ',' => $commas, - '>' => $child, - '~' => $sibling, - '+' => $adjacent, - '[' => $lbracket, - ']' => $rbracket, - '(' => $lparenthesis, - ')' => $rparenthesis, - 'lower-case' => $lowercasefunction - ); - - $this->rules = array( - ' ' => $spaces, - ',' => $commas - ); - - $this->allSiblingsToken = self::uniqueToken($query, 'allSiblings'); - } - - private function toXPath($query) - { - if (isset(self::$cache[$query])) { - return self::$cache[$query]; - } - - $this->tokens($query); - - $query = preg_replace_callback('#\[(\w+)(.)?[=]([^"\'])(.*?)\]#', array($this, 'putQuotes'), $query); - - $preventToken = $this->prevent[' ']; - - $caseinsensitive = '[\\1\\2=\\3' . $preventToken . 'i]'; - - $query = preg_replace('#\[(\w+)(.)?[=](.*?) i\]#', $caseinsensitive, $query); - - $query = preg_replace_callback('#\:contains\(([^"\'])(.*?)\)#', array($this, 'putQuotes'), $query); - - $query = preg_replace_callback('#(["\'])(?:\\\\.|[^\\\\])*?\\1#', array($this, 'preventInQuotes'), $query); - - $query = preg_replace('#(\s+)?([>+~\s])(\s+)?#', '\\2', $query); - - $queries = explode(',', $query); - - foreach ($queries as &$descendants) { - $descendants = explode(' ', trim($descendants)); - - foreach ($descendants as &$descendant) { - foreach ($this->qxs as &$qx) { - $r = strtr($qx[1], $this->rules); - - $descendant = str_replace($preventToken . 'i]', ' i]', $descendant); - $descendant = trim(preg_replace($qx[0], $r, $descendant)); - } - - $childs = explode('>', $descendant); - - foreach ($childs as &$child) { - $child = $this->siblingConvert($child); - } - - $descendant = implode('/', $childs); - } - - $descendants = implode('//', $descendants); - } - - $query = implode('|', $queries); - - $query = preg_replace('#\[(\d+)\]#', $this->prevent['['] . '\\1' . $this->prevent[']'], $query); - - $query = str_replace('][', ' and ', $query); - - $query = preg_replace('#( and |\[)(preceding-sibling::(\*|\w+))\[1 and #', '\\1\\2[1][', $query); - - $query = preg_replace('#lower-case\((.*?)\)#', 'translate(\\1,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")', $query); - - $queries = null; - - $restore = array_combine(array_values($this->prevent), array_keys($this->prevent)); - - return self::$cache[$query] = '//' . strtr($query, $restore); - } - - private function putQuotes($arg) - { - if (stripos($arg[0], ':contains') === 0) { - return ':contains("' . addcslashes($arg[1] . $arg[2], '"') . '")'; - } - - return sprintf('[%s%s="%s"]', $arg[1], $arg[2], addcslashes($arg[3] . $arg[4], '"')); - } - - private function preventInQuotes($arg) - { - $quote = $arg[1]; - $inValue = substr($arg[0], 1, -1); - - if (strpos($inValue, $quote) === false) { - return strtr($arg[0], $this->prevent); - } - - $quotec = $quote === '"' ? '\'' : '"'; - $unes = '\\' . $quote; - $glue = $quote . ',' . $quotec . $quote . $quotec . ',' . $quote; - - $inValue = str_replace($unes, $quote, $inValue); - - $inValue = 'concat(' . $quote . implode($glue, explode($quote, $inValue)) . $quote . ')'; - - return strtr($inValue, $this->prevent); - } - - private function siblingConvert($query) - { - $query = preg_replace('/([+~])([^+~]+)/', '\\1' . $this->allSiblingsToken . '\\2', $query); - - $siblings = explode($this->allSiblingsToken, $query); - - $last = array_pop($siblings); - - $preceding = ''; - - foreach ($siblings as $sibling) { - $i = substr($sibling, -1) === '+' ? '[1]' : ''; - - $sibling = substr($sibling, 0, -1); - - $sibling = preg_replace('/^\*(.*)$/', '[(preceding-sibling::*' . $i . '\\1' . $preceding . ')]', $sibling); - - $sibling = preg_replace('/^(\w+)(.*)$/', '[(preceding-sibling::*' . $i . '[name()="\\1"])\\2' . $preceding . ']', $sibling); - - $preceding = $sibling; - } - - $siblings = null; - - return $last . $preceding; - } - - private static function uniqueToken($query, $key, $token = null) - { - if ($token === null) { - $token = time(); - } - - $rt = "`{$key}:{$token}`"; - - return strpos($query, (string) $token) === false ? preg_quote($rt) : self::uniqueToken($query, $key, ++$token); - } } diff --git a/src/Experimental/Exception.php b/src/Experimental/Exception.php index c2fbea2..6b6cb6a 100644 --- a/src/Experimental/Exception.php +++ b/src/Experimental/Exception.php @@ -9,25 +9,6 @@ namespace Inphinit\Experimental; -class Exception extends \Exception +class Exception extends \Inphinit\Exception { - /** - * Raise an exception - * - * @param string $message - * @param int $trace - * @param int $code - * @return void - */ - public function __construct($message = null, $trace = 0, $code = 0) - { - if ($trace > 0) { - $data = Debug::caller($trace); - - $this->file = $data['file']; - $this->line = $data['line']; - } - - parent::__construct($message, $code); - } } diff --git a/src/Experimental/File.php b/src/Experimental/File.php index d3eb61d..9f05c96 100644 --- a/src/Experimental/File.php +++ b/src/Experimental/File.php @@ -27,7 +27,7 @@ public static function portion($path, $offset = 0, $maxLen = 1024) { self::fullpath($path); - return file_get_contents($path, false, null, $offset, $maxLen); + return parent::portion($path, $offset, $maxLen); } /** @@ -43,29 +43,7 @@ public static function lines($path, $offset = 0, $maxLines = 32) { self::fullpath($path); - $i = 0; - $output = ''; - $max = $maxLines + $offset - 1; - - $handle = fopen($path, 'rb'); - - while (false === feof($handle)) { - $data = fgets($handle); - - if ($i >= $offset) { - $output .= $data; - - if ($i === $max) { - break; - } - } - - ++$i; - } - - fclose($handle); - - return $output; + return parent::lines($path, $offset, $maxLines); } /** diff --git a/src/Experimental/Maintenance.php b/src/Experimental/Maintenance.php index 2a142c8..bda0617 100644 --- a/src/Experimental/Maintenance.php +++ b/src/Experimental/Maintenance.php @@ -9,65 +9,6 @@ namespace Inphinit\Experimental; -use Inphinit\App; - -class Maintenance +class Maintenance extends \Inphinit\Maintenance { - /** - * Down site to maintenance mode - * - * @return bool - */ - public static function down() - { - return static::enable(true); - } - - /** - * Up site - * - * @return bool - */ - public static function up() - { - return static::enable(false); - } - - /** - * Enable/disable maintenance mode - * - * @param bool $enable - * @return bool - */ - protected static function enable($enable) - { - $config = Config::load('config'); - - if ($config->get('maintenance') === $enable) { - return true; - } - - $config->set('maintenance', $enable); - - return $config->save(); - } - - /** - * Up the site only in certain conditions, eg. the site administrator of the IP. - * - * @param callable $callback - * @return void - */ - public static function ignoreif($callback) - { - if (is_callable($callback)) { - App::on('init', function () use ($callback) { - if ($callback()) { - App::env('maintenance', false); - } - }); - } else { - throw new Exception('Invalid callback'); - } - } } diff --git a/src/Experimental/Session.php b/src/Experimental/Session.php index fbfef80..e49a037 100644 --- a/src/Experimental/Session.php +++ b/src/Experimental/Session.php @@ -14,7 +14,6 @@ class Session implements \IteratorAggregate { private $handle; - private $iterator; private $savepath; private $prefix = '~sess_'; private $data = array(); @@ -120,8 +119,6 @@ public function commit($unlock = true) if ($unlock) { flock($this->handle, LOCK_UN); } - - $this->iterator = new \ArrayIterator($this->data); } /** @@ -206,8 +203,6 @@ private function read() } flock($this->handle, LOCK_UN); - - $this->iterator = new \ArrayIterator($this->data); } private function getData() @@ -308,8 +303,6 @@ public function __set($name, $value) $this->insertions[$name] = $value; $this->data = $this->insertions + $this->data; - - $this->iterator = new \ArrayIterator($this->data); } /** @@ -335,8 +328,6 @@ public function __unset($name) unset($this->data[$name], $this->insertions[$name]); $this->deletions[$name] = true; - - $this->iterator = new \ArrayIterator($this->data); } /** @@ -358,7 +349,7 @@ public function __unset($name) */ public function getIterator() { - return $this->iterator; + return new \ArrayIterator($this->data); } public function __destruct() @@ -371,7 +362,6 @@ public function __destruct() $this->opts = $this->data = - $this->iterator = $this->deletions = $this->insertions = $this->handle = null; diff --git a/src/Inphinit/App.php b/src/Inphinit/App.php index 426ba3e..a86f7ad 100644 --- a/src/Inphinit/App.php +++ b/src/Inphinit/App.php @@ -10,13 +10,13 @@ namespace Inphinit; use Inphinit\Http\Response; -use Inphinit\Viewing\View; use Inphinit\Routing\Route; +use Inphinit\Viewing\View; class App { /** Inphinit framework version */ - const VERSION = '0.5.12'; + const VERSION = '0.5.13'; private static $events = array(); private static $configs = array(); diff --git a/src/Inphinit/Config.php b/src/Inphinit/Config.php new file mode 100644 index 0000000..55a1c42 --- /dev/null +++ b/src/Inphinit/Config.php @@ -0,0 +1,208 @@ +) + * + * @param string $path + * @throws \Inphinit\Exception + * @return void + */ + public function __construct($path) + { + $this->path = 'application/Config/' . strtr($path, '.', '/') . '.php'; + + $this->reload(); + } + + /** + * Create a Negotiation instance + * + * @param string $path + * @throws \Inphinit\Exception + * @return \Inphinit\Config + */ + public static function load($path) + { + self::$exceptionlevel = 4; + + return new static($path); + } + + /** + * Reload configuration from file + * + * @param string $path + * @throws \Inphinit\Exception + * @return \Inphinit\Config + */ + public function reload() + { + $level = self::$exceptionlevel; + + self::$exceptionlevel = 2; + + if (false === \Inphinit\File::exists(INPHINIT_PATH . $this->path)) { + throw new Exception('File not found ' . $this->path, $level); + } + + foreach (\UtilsSandboxLoader($this->path) as $key => $value) { + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Reload configuration from file + * + * @return bool + */ + public function save() + { + if (Storage::createFolder('tmp/cfg')) { + $wd = preg_replace('#,(\s+|)\)#', '$1)', var_export($this->data, true)); + $path = Storage::temp('path); + + unlink($path); + + return $response; + } + } + + return false; + } + + /** + * Get all values like array or get specific item by level (multidimensional) using path + * + * @param string $path (optional) Path with "dots" + * @param string $alternative (optional) alternative value does not find the selected value, default is null + * @return mixed + */ + public function get($path = null, $alternative = null) + { + if ($path === null) { + return $this->data; + } + + return Helper::extract($path, $this->data, $alternative); + } + + /** + * Set value by path in specific level (multidimensional) + * + * @param string $path Path with "dots" + * @param mixed $value Define value + * @return \Inphinit\Config + */ + public function set($path, $value) + { + $paths = explode('.', $path); + + $key = array_shift($paths); + + $tree = $value; + + foreach (array_reverse($paths) as $item) { + $tree = array($item => $tree); + } + + $this->data[$key] = $tree; + + $tree = null; + + return $this; + } + + /** + * Magic method for get specific item by -> + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + if (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } + } + + /** + * Magic method for set value (this method don't save data) + * + * @param string $name + * @param mixed $value + * @return void + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * Magic method for check if value exists in top-level + * + * @param string $name + * @return bool + */ + public function __isset($name) + { + return array_key_exists($name, $this->data); + } + + /** + * Magic method for unset variable with `unset()` function + * + * @param string $name + * @return void + */ + public function __unset($name) + { + unset($this->data[$name]); + } + + /** + * Allow iteration with `for`, `foreach` and `while` + * + * Example: + *
+     * 
+     * $foo = new Config('file'); //or Config::load('file')
+     *
+     * foreach ($foo as $key => $value) {
+     *     var_dump($key, $value);
+     *     echo EOL;
+     * }
+     * 
+     * 
+ * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + public function __destruct() + { + $this->data = null; + } +} diff --git a/src/Inphinit/Debug.php b/src/Inphinit/Debug.php new file mode 100644 index 0000000..ccac6f6 --- /dev/null +++ b/src/Inphinit/Debug.php @@ -0,0 +1,337 @@ +Fatal error: $message in $file on line $line"); + } + + $data = self::details($type, $message, $file, $line); + + if (!headers_sent() && strpos(Request::header('accept'), 'application/json') === 0) { + ob_start(); + + self::unregister(); + + Response::cache(0); + Response::type('application/json'); + + echo json_encode($data); + + App::stop(500); + } + + if (in_array($type, self::$fatal)) { + View::dispatch(); + } + + self::render(self::$views['error'], $data); + } + + /** + * Render a View to show performance, memory and time to display page + * + * @return void + */ + public static function renderPerformance() + { + if (isset(self::$views['performance'])) { + self::render(self::$views['performance'], self::performance()); + } + } + + /** + * Render a View to show performance and show declared classes + * + * @return void + */ + public static function renderClasses() + { + if (isset(self::$views['classes'])) { + self::render(self::$views['classes'], array( + 'classes' => self::classes() + )); + } + } + + /** + * Register a debug views + * + * @param string $type + * @param string $view + * @throws \Inphinit\Exception + * @return void + */ + public static function view($type, $view) + { + if ($view !== null && View::exists($view) === false) { + throw new Exception($view . ' view is not found', 2); + } + + $callRender = array( '\\' . get_called_class(), 'render' . ucfirst($type) ); + + if ($type === 'error') { + App::on('error', $callRender); + + if (empty(self::$displayErrors)) { + self::$displayErrors = ini_get('display_errors'); + + if (function_exists('ini_set')) { + ini_set('display_errors', '0'); + } + } + } elseif ($type === 'classes' || $type === 'performance') { + App::on('terminate', $callRender); + } elseif ($type !== 'before') { + throw new Exception($type . ' is not valid event', 2); + } + + self::$views[$type] = $view; + } + + /** + * Get memory usage and you can also use it to calculate runtime. + * + * @return array + */ + public static function performance() + { + return array( + 'usage' => memory_get_usage() / 1024, + 'peak' => memory_get_peak_usage() / 1024, + 'real' => memory_get_peak_usage(true) / 1024, + 'time' => microtime(true) - INPHINIT_START + ); + } + + /** + * Get declared classes + * @return array + */ + public static function classes() + { + $objs = array(); + + foreach (get_declared_classes() as $value) { + $value = ltrim($value, '\\'); + $cname = new \ReflectionClass($value); + + if (false === $cname->isInternal()) { + $objs[$value] = $cname->getDefaultProperties(); + } + } + + $cname = null; + + return $objs; + } + + /** + * Get snippet from a file + * + * @param string $file + * @param int $line + * @return array|null + */ + public static function source($file, $line) + { + if ($line <= 0 || is_file($file) === false) { + return null; + } elseif ($line > 5) { + $init = $line - 6; + $max = 10; + $breakpoint = 6; + } else { + $init = 0; + $max = 5; + $breakpoint = $line; + } + + $preview = preg_split('#\r\n|\n#', File::lines($file, $init, $max)); + + if (count($preview) !== $breakpoint && trim(end($preview)) === '') { + array_pop($preview); + } + + return array( + 'breakpoint' => $breakpoint, + 'preview' => $preview + ); + } + + /** + * Get backtrace php scripts + * + * @param int $level + * @return array|null + */ + public static function caller($level = 0) + { + $trace = debug_backtrace(0); + + foreach ($trace as $key => &$value) { + if (isset($value['file'])) { + self::evalFileLocation($value['file'], $value['line']); + } else { + unset($trace[$key]); + } + } + + $trace = array_values($trace); + + if ($level < 0) { + return $trace; + } elseif (isset($trace[$level])) { + return $trace = $trace[$level]; + } + } + + /** + * Convert error message in a link, see `system/config/debug.php` + * + * @param string $message + * @return string + */ + public static function searcherror($message) + { + if (self::$linkSearchError === null) { + self::$linkSearchError = Config::load('debug')->get('searcherror'); + } + + $link = self::$linkSearchError; + + if (strpos($link, '%error%') === -1) { + return $message; + } + + return preg_replace_callback('#^([\s\S]+?)\s+in\s+([\s\S]+?:\d+)|^([\s\S]+?)$#', function ($matches) use ($link) { + $error = empty($matches[3]) ? $matches[1] : $matches[3]; + + $url = str_replace('%error%', urlencode($error), $link); + $url = htmlentities($url); + + return '' . $error . '' . + (empty($matches[2]) ? '' : (' in ' . $matches[2])); + }, $message); + } + + private static function render($view, $data) + { + if (!self::$showBeforeView && isset(self::$views['before'])) { + self::$showBeforeView = true; + View::render(self::$views['before']); + } + + View::render($view, $data); + } + + private static function details($type, $message, $file, $line) + { + $match = array(); + + if (preg_match('#called in ([\s\S]+?) on line (\d+)#', $message, $match)) { + $file = $match[1]; + $line = (int) $match[2]; + } + + self::evalFileLocation($file, $line); + + switch ($type) { + case E_PARSE: + $message = 'Parse error: ' . $message; + break; + + case E_DEPRECATED: + case E_USER_DEPRECATED: + $message = 'Deprecated: ' . $message; + break; + + case E_ERROR: + case E_USER_ERROR: + case E_RECOVERABLE_ERROR: + $message = 'Fatal error: ' . $message; + break; + + case E_WARNING: + case E_USER_WARNING: + $message = 'Warning: ' . $message; + break; + + case E_NOTICE: + case E_USER_NOTICE: + $message = 'Notice: ' . $message; + break; + } + + return array( + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'source' => $line > -1 ? self::source($file, $line) : null + ); + } + + private static function evalFileLocation(&$file, &$line) + { + if (preg_match('#(.*?)\((\d+)\) : eval\(\)\'d code#', $file, $match)) { + $file = $match[1]; + $line = (int) $match[2]; + } + } +} diff --git a/src/Inphinit/Dir.php b/src/Inphinit/Dir.php new file mode 100644 index 0000000..f2beb1a --- /dev/null +++ b/src/Inphinit/Dir.php @@ -0,0 +1,188 @@ +handle = opendir($path); + + if ($this->handle === false) { + throw new Exception('Failed to open folder', 2); + } + + $path = strtr(realpath($path), '\\', '/'); + + $this->path = rtrim($path, '/') . '/'; + + $this->find(0); + } + + /** + * Return items from root project folder (probably, will depend on the setting + * of the `INPHINIT_ROOT` constant) + * + * @return \Inphinit\Dir + */ + public static function root() + { + return new static(INPHINIT_ROOT); + } + + /** + * Return items from storage folder + * + * @return \Inphinit\Dir + */ + public static function storage() + { + return new static(INPHINIT_PATH . 'storage'); + } + + /** + * Return items from application folder + * + * @return \Inphinit\Dir + */ + public static function application() + { + return new static(INPHINIT_PATH . 'application'); + } + + /** + * Resets the directory stream to the beginning of the directory + * + * @return void + */ + public function rewind() + { + $this->position = $this->find(0); + } + + /** + * Get current file with type, path and filename + * The entries are returned in the order in which they are stored by the filesystem. + * + * @return stdClass|null + */ + public function current() + { + if ($this->item !== false) { + $current = $this->path . $this->item; + + return (object) array( + 'type' => filetype($current), + 'path' => $current, + 'name' => $this->item, + 'position' => $this->position + ); + } + } + + /** + * Get current position in handle + * + * @return int + */ + public function key() + { + return $this->position; + } + + /** + * Move forward to next file + * + * @return void + */ + public function next() + { + $this->item = readdir($this->handle); + + if ($this->item !== false) { + ++$this->position; + } + } + + /** + * Check if pointer is valid + * + * @return bool + */ + public function valid() + { + return $this->item !== false; + } + + /** + * Count files in folder, can br used by `count($instance)` funciton + * + * @return int + */ + public function count() + { + if ($this->size === -1) { + $this->size = $this->find(-1); + + //Restore position + if ($this->position > 0) { + $this->find($this->position); + } + } + + return $this->size; + } + + private function find($pos) + { + rewinddir($this->handle); + + $current = 0; + $break = $pos !== -1; + + while (false !== ($item = readdir($this->handle))) { + // if ($item === '.' || $item === '..') { + // continue; + // } elseif ($current === $pos) { + if ($item !== '.' && $item !== '..' && $current === $pos) { + $this->item = $item; + + if ($break) { + break; + } + } + + ++$current; + } + + return $current !== $pos ? $current : 0; + } + + public function __destruct() + { + if ($this->handle) { + closedir($this->handle); + $this->handle = null; + } + } +} diff --git a/src/Inphinit/Dom/Document.php b/src/Inphinit/Dom/Document.php new file mode 100644 index 0000000..0b1c2d6 --- /dev/null +++ b/src/Inphinit/Dom/Document.php @@ -0,0 +1,525 @@ +reporting(LIBXML_ERR_FATAL, LIBXML_ERR_ERROR)` + * + * + * + * @param int $args,... + * @return void + */ + public function reporting() + { + $this->levels = func_get_args(); + } + + /** + * Convert a array in node elements + * + * @param array|\Traversable $data + * @throws \Inphinit\Dom\DomException + * @return void + */ + public function fromArray(array $data) + { + if (empty($data)) { + throw new DomException('Array is empty', 2); + } elseif (count($data) > 1) { + throw new DomException('Root array accepts only a key', 2); + } + + $root = key($data); + + if (self::validTag($root) === false) { + throw new DomException('Invalid root <' . $root . '> tag', 2); + } + + if ($this->documentElement) { + $this->removeChild($this->documentElement); + } + + $this->enableRestoreInternal(true); + + $this->generate($this, $data, 2); + + $this->raise($this->exceptionlevel); + + $this->enableRestoreInternal(false); + } + + /** + * Convert DOM to JSON string + * + * @param bool $format + * @param int $options `JSON_HEX_QUOT`, `JSON_HEX_TAG`, `JSON_HEX_AMP`, `JSON_HEX_APOS`, `JSON_NUMERIC_CHECK`, `JSON_PRETTY_PRINT`, `JSON_UNESCAPED_SLASHES`, `JSON_FORCE_OBJECT`, `JSON_PRESERVE_ZERO_FRACTION`, `JSON_UNESCAPED_UNICODE`, `JSON_PARTIAL_OUTPUT_ON_ERROR`. The behaviour of these constants is described in http://php.net/manual/en/json.constants.php + * + * @return string + */ + public function toJson($format = Document::MINIMAL, $options = 0) + { + $this->exceptionlevel = 4; + + $json = json_encode($this->toArray($format), $options); + + $this->exceptionlevel = 3; + + return $json; + } + + /** + * Convert DOM to Array + * + * @param int $type + * @throws \Inphinit\Dom\DomException + * @return array + */ + public function toArray($type = Document::SIMPLE) + { + switch ($type) { + case Document::MINIMAL: + $this->simple = false; + $this->complete = false; + break; + + case Document::SIMPLE: + $this->simple = true; + break; + + case Document::COMPLETE: + $this->complete = true; + break; + + default: + throw new DomException('Invalid type', 2); + } + + return $this->getNodes($this->childNodes); + } + + /** + * Magic method, return a well-formed XML string + * + * Example: + *
+     * 
+     * $xml = new Dom;
+     *
+     * $xml->fromArray(array(
+     *     'foo' => 'bar'
+     * ));
+     *
+     * echo $xml;
+     * 
+     * 
+ * + * @return string + */ + public function __toString() + { + return $this->saveXML(); + } + + /** + * Save file to location + * + * @param string $path + * @param int $format Support XML, HTML, and JSON + * @throws \Inphinit\Dom\DomException + * @return void + */ + public function save($path, $format = Document::XML) + { + switch ($format) { + case Document::XML: + $format = 'saveXML'; + break; + case Document::HTML: + $format = 'saveHTML'; + break; + case Document::JSON: + $format = 'toJson'; + break; + default: + throw new DomException('Invalid format', 2); + } + + if (Storage::createFolder('tmp/dom')) { + $tmp = Storage::temp($this->$format(), 'tmp/dom'); + } else { + $tmp = false; + } + + if ($tmp === false) { + throw new DomException('Can\'t create tmp file', 2); + } elseif (copy($tmp, $path) === false) { + throw new DomException('Can\'t copy tmp file to ' . $path, 2); + } else { + unlink($tmp); + } + } + + /** + * Get namespace attributes from root element or specific element + * + * @param \DOMElement $element + * @return void + */ + public function getNamespaces(\DOMElement $element = null) + { + if ($this->xpath === null) { + $this->xpath = new \DOMXPath($this); + } + + if ($element === null) { + $nodes = $this->xpath->query('namespace::*'); + $element = $this->documentElement; + } else { + $nodes = $this->xpath->query('namespace::*', $element); + } + + $ns = array(); + + if ($nodes) { + foreach ($nodes as $node) { + $arr = $element->getAttribute($node->nodeName); + + if ($arr) { + $ns[$node->nodeName] = $arr; + } + } + + $nodes = null; + } + + return $ns; + } + + /** + * Load XML from a string + * + * @param string $source + * @param int $options + * @throws \Inphinit\Dom\DomException + * @return mixed + */ + public function loadXML($source, $options = 0) + { + return $this->resource('loadXML', $source, $options); + } + + /** + * Load XML from a file + * + * @param string $filename + * @param int $options + * @throws \Inphinit\Dom\DomException + * @return mixed + */ + public function load($filename, $options = 0) + { + return $this->resource('load', $filename, $options); + } + + /** + * Load HTML from a string + * + * @param string $source + * @param int $options + * @throws \Inphinit\Dom\DomException + * @return mixed + */ + public function loadHTML($source, $options = 0) + { + return $this->resource('loadHTML', $source, $options); + } + + /** + * Load HTML from a file + * + * @param string $filename + * @param int $options + * @throws \Inphinit\Dom\DomException + * @return mixed + */ + public function loadHTMLFile($filename, $options = 0) + { + return $this->resource('loadHTMLFile', $filename, $options); + } + + /** + * Use query-selector like CSS, jQuery, querySelectorAll + * + * @param string $selector + * @param \DOMNode $context + * @return \DOMNodeList + */ + public function query($selector, \DOMNode $context = null) + { + $this->enableRestoreInternal(true); + + if ($this->selector === null) { + $this->selector = new Selector($this); + } + + $nodes = $this->selector->get($selector, $context); + + $level = $this->exceptionlevel; + + $this->exceptionlevel = 3; + + $this->raise($level); + + $this->enableRestoreInternal(false); + + return $nodes; + } + + /** + * Use query-selector like CSS, jQuery, querySelector + * + * @param string $selector + * @param \DOMNode $context + * @return \DOMNode + */ + public function first($selector, \DOMNode $context = null) + { + $this->exceptionlevel = 4; + + $nodes = $this->query($selector, $context); + + $node = $nodes ? $nodes->item(0) : null; + + $nodes = null; + + return $node; + } + + private function resource($function, $from, $options) + { + $this->enableRestoreInternal(true); + + $resource = PHP_VERSION_ID >= 50400 ? parent::$function($from, $options) : parent::$function($from); + + $this->raise(4); + + $this->enableRestoreInternal(false); + + return $resource; + } + + private function enableRestoreInternal($enable) + { + \libxml_clear_errors(); + + if ($enable) { + $this->internalErr = \libxml_use_internal_errors(true); + } else { + \libxml_use_internal_errors($this->internalErr); + } + } + + private function raise($debuglvl) + { + $err = \libxml_get_errors(); + + if (isset($err[0]->level) && in_array($err[0]->level, $this->levels, true)) { + throw new DomException(null, $debuglvl); + } + + \libxml_clear_errors(); + } + + private function generate(\DOMNode $node, $data, $errorLevel) + { + if (is_array($data) === false) { + $node->textContent = $data; + return; + } + + $nextLevel = $errorLevel + 1; + + foreach ($data as $key => $value) { + if ($key === '@comments') { + continue; + } elseif ($key === '@contents') { + $this->generate($node, $value, $nextLevel); + } elseif ($key === '@attributes') { + self::setAttributes($node, $value); + } elseif (self::validTag($key)) { + if (Helper::seq($value)) { + foreach ($value as $subvalue) { + $this->generate($node, array($key => $subvalue), $nextLevel); + } + } elseif (is_array($value)) { + $this->generate($this->add($key, '', $node), $value, $nextLevel); + } else { + $this->add($key, $value, $node); + } + } else { + throw new DomException('Invalid tag: <' . $key . '>', $nextLevel); + } + } + } + + private static function validTag($tagName) + { + return preg_match('#^([a-z_](\w+|)|[a-z_](\w+|):[a-z_](\w+|))$#i', $tagName) > 0; + } + + private function add($name, $value, \DOMNode $node) + { + $newdom = $this->createElement($name, $value); + $node->appendChild($newdom); + return $newdom; + } + + private static function setAttributes(\DOMNode $node, array &$attributes) + { + foreach ($attributes as $name => $value) { + $node->setAttribute($name, $value); + } + } + + private function getNodes($nodes) + { + $items = array(); + + if ($nodes) { + foreach ($nodes as $node) { + if ($node->nodeType === \XML_ELEMENT_NODE && ($this->complete || $this->simple || ctype_alnum($node->nodeName))) { + $items[$node->nodeName][] = $this->nodeContents($node); + } + } + + if (empty($items) === false) { + self::simplify($items); + } + } + + return $items; + } + + private function nodeContents(\DOMElement $node) + { + $extras = array( '@attributes' => array() ); + + if ($this->complete) { + foreach ($node->attributes as $attribute) { + $extras['@attributes'][$attribute->nodeName] = $attribute->nodeValue; + } + } + + if ($this->complete && ($ns = $this->getNamespaces($node))) { + $extras['@attributes'] = $extras['@attributes'] + $ns; + } + + if ($node->getElementsByTagName('*')->length) { + $r = $this->getNodes($node->childNodes) + ( + empty($extras['@attributes']) ? array() : $extras + ); + } elseif (empty($extras['@attributes'])) { + return $node->nodeValue; + } else { + $r = array($node->nodeValue) + $extras; + } + + self::simplify($r); + + return $r; + } + + private static function simplify(&$items) + { + if (self::toContents($items)) { + foreach ($items as $name => &$item) { + if (is_array($item) === false || strpos($name, '@') !== false) { + continue; + } + + if (count($item) === 1 && isset($item[0])) { + $item = $item[0]; + } else { + self::toContents($item); + } + } + } + } + + private static function toContents(&$item) + { + if (count($item) > 1 && isset($item[0]) && isset($item[1]) === false) { + $item['@contents'] = $item[0]; + unset($item[0]); + + return false; + } + + return true; + } +} diff --git a/src/Inphinit/Dom/DomException.php b/src/Inphinit/Dom/DomException.php new file mode 100644 index 0000000..baa015b --- /dev/null +++ b/src/Inphinit/Dom/DomException.php @@ -0,0 +1,40 @@ +message)) { + $message = trim($err[0]->message); + } + + if (isset($err[0]->file) && $err[0]->line > 0) { + $this->file = preg_replace('#^file:/(\w+:)#i', '$1', $err[0]->file); + $this->line = $err[0]->line; + $this->code = $err[0]->code; + $trace = 0; + } + + parent::__construct($message, $trace); + } +} diff --git a/src/Inphinit/Dom/Selector.php b/src/Inphinit/Dom/Selector.php new file mode 100644 index 0000000..c07ea75 --- /dev/null +++ b/src/Inphinit/Dom/Selector.php @@ -0,0 +1,258 @@ ++~])([^\w*\s])/', '\\1*\\2' ), + array( '/\[(.*?)\]/', '[@\\1\\2]' ), + array( '/\.(\w+)/', '[@class~="\\1" i]' ), + array( '/#(\w+)/', '[@id="\\1" i]' ), + array( '/\:lang\(([\w\-]+)\)/i', '[@lang|="\\1" i]' ), + array( '/([>+~]|^)\:nth-(last-)?(child|of-type)\(n\)/i', '\\1*' ), + array( '/\:nth-(last-)?(child|of-type)\(n\)/i', '' ), + array( '/\:first-child/', ':nth-child(1)'), + array( '/\:nth-child\(odd\)/i', ':nth-child(2n+1)' ), + array( '/\:nth-child\(even\)/i', ':nth-child(2n)' ), + array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)\)/i', '\\1*[position()=\\3]\\2' ), + array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)n\)/i', '\\1*[(position() mod \\3=0)]\\2' ), + array( '/([>+~])?\*([^>+~]+)?\:nth-child\((\d+)n\+(\d+)\)/i', '\\1*[(position() mod \\3=\\4)]\\2' ), + array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)\)/i', '\\1*[name()="\\2" and position()=\\4]\\3' ), + array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)n\)/i', '\\1*[name()="\\2" and (position() mod \\4=0)]\\3' ), + array( '/([>+~])?(\w+)([^>+~]+)?\:nth-child\((\d+)n\+(\d+)\)/i', '\\1*[name()="\\2" and (position() mod \\4=\\5)]\\3' ), + array( '/([>+~])?\*([^>+~]+)?\:only-child/i', '\\1*[last()=1]\\2' ), + array( '/([>+~])?\*([^>+~]+)?\:last-child/i', '\\1*[position()=last()]\\2' ), + array( '/([>+~])?(\w+)([^>+~]+)?\:only-child/i', '\\1*[name()="\\2" and last()=1]\\3' ), + array( '/([>+~])?(\w+)([^>+~]+)?\:last-child/i', '\\1*[name()="\\2" and position()=last()]\\3' ), + array( '/\:empty/i', '[not(text())]'), + array( '/\[(@\w+)(.)?=(.*?) i]/', '[lower-case(\\1)\\2=lower-case(\\3)]' ), + array( '/\[(@\w+|lower-case\(@\w+\))\*=(.*?)\]/', '[contains(\\1,\\2)]' ), + array( '/\[(@\w+|lower-case\(@\w+\))\^=(.*?)\]/', '[starts-with(\\1,\\2)]' ), + array( '/\[(@\w+|lower-case\(@\w+\))\~=(.*?)\]/', '[contains(concat(" ",\\1," "),concat(" ",\\2," "))]' ), + array( '/\[(@\w+|lower-case\(@\w+\))\|=(.*?)\]/', '[starts-with(concat(\\1,"-"),concat(\\2,"-"))]' ), + array( '/\[(@\w+|lower-case\(@\w+\))\$=(.*?)\]/', '[substring(\\1,string-length(\\1)-2)=\\2]' ), + array( '/\:contains\((.*?)\)/i', '[contains(.,\\1)]' ), + array( '/\:contains-child\((.*?)\)/i', '[text()[contains(.,\\1)]]' ) + ); + + /** + * Count all nodes matching the given CSS selector + * + * @param string $selector + * @param \DOMNode $context + * @param bool $registerNodeNS + * @return \DOMNodeList + */ + public function count($selector, \DOMNode $context = null, $registerNodeNS = true) + { + return $this->exec('evaluate', $selector, $context, $registerNodeNS); + } + + /** + * Returns a \DOMNodeList containing all nodes matching the given CSS selector + * + * @param string $selector + * @param \DOMNode $context + * @param bool $registerNodeNS + * @return \DOMNodeList + */ + public function get($selector, \DOMNode $context = null, $registerNodeNS = true) + { + return $this->exec('query', $selector, $context, $registerNodeNS); + } + + private function exec($method, $query, $context, $registerNodeNS) + { + $query = $this->toXPath($query); + + if (PHP_VERSION_ID >= 50303) { + return $this->$method($query, $context, $registerNodeNS); + } elseif ($context !== null) { + return $this->$method($query, $context); + } + + return $this->$method($query); + } + + private function tokens($query) + { + $dot = self::uniqueToken($query, 'dot'); + $hash = self::uniqueToken($query, 'hash'); + $spaces = self::uniqueToken($query, 'space'); + $commas = self::uniqueToken($query, 'comma'); + $child = self::uniqueToken($query, 'child'); + $sibling = self::uniqueToken($query, 'sibling'); + $adjacent = self::uniqueToken($query, 'adjacent'); + $lbracket = self::uniqueToken($query, 'lbracket'); + $rbracket = self::uniqueToken($query, 'rbracket'); + $lparenthesis = self::uniqueToken($query, 'lparenthesis'); + $rparenthesis = self::uniqueToken($query, 'rparenthesis'); + $lowercasefunction = self::uniqueToken($query, 'lowercase'); + + $this->prevent = array( + '.' => $dot, + '#' => $hash, + ' ' => $spaces, + ',' => $commas, + '>' => $child, + '~' => $sibling, + '+' => $adjacent, + '[' => $lbracket, + ']' => $rbracket, + '(' => $lparenthesis, + ')' => $rparenthesis, + 'lower-case' => $lowercasefunction + ); + + $this->rules = array( + ' ' => $spaces, + ',' => $commas + ); + + $this->allSiblingsToken = self::uniqueToken($query, 'allSiblings'); + } + + private function toXPath($query) + { + if (isset(self::$cache[$query])) { + return self::$cache[$query]; + } + + $this->tokens($query); + + $query = preg_replace_callback('#\[(\w+)(.)?[=]([^"\'])(.*?)\]#', array($this, 'putQuotes'), $query); + + $preventToken = $this->prevent[' ']; + + $caseinsensitive = '[\\1\\2=\\3' . $preventToken . 'i]'; + + $query = preg_replace('#\[(\w+)(.)?[=](.*?) i\]#', $caseinsensitive, $query); + + $query = preg_replace_callback('#\:contains\(([^"\'])(.*?)\)#', array($this, 'putQuotes'), $query); + + $query = preg_replace_callback('#(["\'])(?:\\\\.|[^\\\\])*?\\1#', array($this, 'preventInQuotes'), $query); + + $query = preg_replace('#(\s+)?([>+~\s])(\s+)?#', '\\2', $query); + + $queries = explode(',', $query); + + foreach ($queries as &$descendants) { + $descendants = explode(' ', trim($descendants)); + + foreach ($descendants as &$descendant) { + foreach ($this->qxs as &$qx) { + $r = strtr($qx[1], $this->rules); + + $descendant = str_replace($preventToken . 'i]', ' i]', $descendant); + $descendant = trim(preg_replace($qx[0], $r, $descendant)); + } + + $childs = explode('>', $descendant); + + foreach ($childs as &$child) { + $child = $this->siblingConvert($child); + } + + $descendant = implode('/', $childs); + } + + $descendants = implode('//', $descendants); + } + + $query = implode('|', $queries); + + $query = preg_replace('#\[(\d+)\]#', $this->prevent['['] . '\\1' . $this->prevent[']'], $query); + + $query = str_replace('][', ' and ', $query); + + $query = preg_replace('#( and |\[)(preceding-sibling::(\*|\w+))\[1 and #', '\\1\\2[1][', $query); + + $query = preg_replace('#lower-case\((.*?)\)#', 'translate(\\1,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")', $query); + + $queries = null; + + $restore = array_combine(array_values($this->prevent), array_keys($this->prevent)); + + return self::$cache[$query] = '//' . strtr($query, $restore); + } + + private function putQuotes($arg) + { + if (stripos($arg[0], ':contains') === 0) { + return ':contains("' . addcslashes($arg[1] . $arg[2], '"') . '")'; + } + + return sprintf('[%s%s="%s"]', $arg[1], $arg[2], addcslashes($arg[3] . $arg[4], '"')); + } + + private function preventInQuotes($arg) + { + $quote = $arg[1]; + $inValue = substr($arg[0], 1, -1); + + if (strpos($inValue, $quote) === false) { + return strtr($arg[0], $this->prevent); + } + + $quotec = $quote === '"' ? '\'' : '"'; + $unes = '\\' . $quote; + $glue = $quote . ',' . $quotec . $quote . $quotec . ',' . $quote; + + $inValue = str_replace($unes, $quote, $inValue); + + $inValue = 'concat(' . $quote . implode($glue, explode($quote, $inValue)) . $quote . ')'; + + return strtr($inValue, $this->prevent); + } + + private function siblingConvert($query) + { + $query = preg_replace('/([+~])([^+~]+)/', '\\1' . $this->allSiblingsToken . '\\2', $query); + + $siblings = explode($this->allSiblingsToken, $query); + + $last = array_pop($siblings); + + $preceding = ''; + + foreach ($siblings as $sibling) { + $i = substr($sibling, -1) === '+' ? '[1]' : ''; + + $sibling = substr($sibling, 0, -1); + + $sibling = preg_replace('/^\*(.*)$/', '[(preceding-sibling::*' . $i . '\\1' . $preceding . ')]', $sibling); + + $sibling = preg_replace('/^(\w+)(.*)$/', '[(preceding-sibling::*' . $i . '[name()="\\1"])\\2' . $preceding . ']', $sibling); + + $preceding = $sibling; + } + + $siblings = null; + + return $last . $preceding; + } + + private static function uniqueToken($query, $key, $token = null) + { + if ($token === null) { + $token = time(); + } + + $rt = "`{$key}:{$token}`"; + + return strpos($query, (string) $token) === false ? preg_quote($rt) : self::uniqueToken($query, $key, ++$token); + } +} diff --git a/src/Inphinit/Exception.php b/src/Inphinit/Exception.php new file mode 100644 index 0000000..22fc3ac --- /dev/null +++ b/src/Inphinit/Exception.php @@ -0,0 +1,33 @@ + 0) { + $data = Debug::caller($trace); + + $this->file = $data['file']; + $this->line = $data['line']; + } + + parent::__construct($message, $code); + } +} diff --git a/src/Inphinit/File.php b/src/Inphinit/File.php index 602ce62..ca865e8 100644 --- a/src/Inphinit/File.php +++ b/src/Inphinit/File.php @@ -122,10 +122,10 @@ public static function mime($path) $mime = false; $size = 0; - if (is_readable($path)) { - if (function_exists('finfo_open')) { - $buffer = file_get_contents($path, false, null, 0, 5012); + if (function_exists('finfo_open')) { + $buffer = file_get_contents($path, false, null, 0, 5012); + if ($buffer) { $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_buffer($finfo, $buffer); finfo_close($finfo); @@ -133,17 +133,20 @@ public static function mime($path) $size = strlen($buffer); $buffer = null; - } elseif (function_exists('mime_content_type')) { - $mime = mime_content_type($path); - $size = filesize($path); } + } elseif (function_exists('mime_content_type')) { + $mime = mime_content_type($path); - //Note: $size >= 0 prevents negative numbers for big files (in x86) - if ($mime !== false && strpos($mime, 'application/') === 0 && $size >= 0 && $size < 2) { - return 'text/plain'; + if ($mime) { + $size = filesize($path); } } + //Note: $size >= 0 prevents negative numbers for big files (in x86) + if ($mime !== false && $size >= 0 && $size < 2 && strpos($mime, 'application/') === 0) { + return 'text/plain'; + } + return $mime; } @@ -157,15 +160,15 @@ public static function mime($path) */ public static function output($path, $length = 102400, $delay = 0) { - if (is_readable($path) === false) { + if (false === ($handle = fopen($path, 'rb'))) { return false; } $buffer = ob_get_level() !== 0; - $handle = fopen($path, 'rb'); - - $length = is_int($length) && $length > 0 ? $length : 102400; + if (is_int($length) && $length > 0) { + $length = 102400; + } while (false === feof($handle)) { echo fread($handle, $length); @@ -181,4 +184,56 @@ public static function output($path, $length = 102400, $delay = 0) flush(); } } + + /** + * Read excerpt from a file + * + * @param string $path + * @param int $offset + * @param int $maxLen + * @throws \Inphinit\Exception + * @return string|bool + */ + public static function portion($path, $offset = 0, $maxLen = 1024) + { + return file_get_contents($path, false, null, $offset, $maxLen); + } + + /** + * Read lines from a file + * + * @param string $path + * @param int $offset + * @param int $maxLine + * @throws \Inphinit\Exception + * @return string|bool + */ + public static function lines($path, $offset = 0, $maxLines = 32) + { + if (false === ($handle = fopen($path, 'rb'))) { + return false; + } + + $i = 0; + $output = ''; + $max = $maxLines + $offset - 1; + + while (false === feof($handle)) { + $data = fgets($handle); + + if ($i >= $offset) { + $output .= $data; + + if ($i === $max) { + break; + } + } + + ++$i; + } + + fclose($handle); + + return $output; + } } diff --git a/src/Inphinit/Helper.php b/src/Inphinit/Helper.php index 35c3736..b95634c 100644 --- a/src/Inphinit/Helper.php +++ b/src/Inphinit/Helper.php @@ -19,13 +19,17 @@ class Helper */ public static function parseVersion($version) { - if (preg_match('#^(\d+)\.(\d+)\.(\d+)(\-([\w.\-]+)|)$#', $version, $match)) { - return (object) array( - 'major' => $match[1], - 'minor' => $match[2], - 'patch' => $match[3], - 'extra' => isset($match[5][0]) ? $match[5] : null + if (preg_match('#^(\d+)\.(\d+)\.(\d+)(-([\da-z]+(\.[\da-z]+)*)(\+([\da-z]+(\.[\da-z]+)*))?)?$#', $version, $match)) { + $matches = array( + 'major' => $matches[1], + 'minor' => $matches[2], + 'patch' => $matches[3], + 'extra' => $matches[4], + 'release' => explode('.', $matches[5]), + 'build' => explode('.', $matches[8]) ); + + return (object) $matches; } return null; @@ -124,7 +128,7 @@ public static function assoc($array) * * @param array $array * @param int $flags See details in https://www.php.net/manual/en/function.sort.php#refsect1-function.sort-parameters - * @return null + * @return void */ public static function ksort(array &$array, $flags = \SORT_REGULAR) { diff --git a/src/Inphinit/Http/Request.php b/src/Inphinit/Http/Request.php index 3645384..a49a075 100644 --- a/src/Inphinit/Http/Request.php +++ b/src/Inphinit/Http/Request.php @@ -46,6 +46,9 @@ public static function is($check) case 'pjax': return strcasecmp(self::header('X-Pjax'), 'true') === 0; + + case 'prefetch': + return strcasecmp(self::header('Purpose') || self::header('X-Moz') || self::header('X-Purpose'), 'prefetch') === 0; } return strcasecmp($_SERVER['REQUEST_METHOD'], $check) === 0; @@ -172,7 +175,7 @@ public static function raw($binary = true) if (PHP_VERSION_ID >= 70224) { return fopen('php://input', $mode); - } else if (self::$rawInput) { + } elseif (self::$rawInput) { return fopen(self::$rawInput, $mode); } @@ -191,7 +194,7 @@ public static function raw($binary = true) return fopen($temp, $mode); } - private static function data(&$data, $key, $alternative = null) + private static function data(&$data, $key, $alternative) { if (empty($data)) { return $alternative; diff --git a/src/Inphinit/Maintenance.php b/src/Inphinit/Maintenance.php new file mode 100644 index 0000000..ee66e8e --- /dev/null +++ b/src/Inphinit/Maintenance.php @@ -0,0 +1,73 @@ +get('maintenance') === $enable) { + return true; + } + + $config->set('maintenance', $enable); + + return $config->save(); + } + + /** + * Up the site only in certain conditions, eg. the site administrator of the IP. + * + * @param callable $callback + * @return void + */ + public static function ignoreif($callback) + { + if (is_callable($callback)) { + App::on('init', function () use ($callback) { + if ($callback()) { + App::env('maintenance', false); + } + }); + } else { + throw new Exception('Invalid callback'); + } + } +} diff --git a/src/Inphinit/Packages.php b/src/Inphinit/Packages.php index 07ace62..665ed37 100644 --- a/src/Inphinit/Packages.php +++ b/src/Inphinit/Packages.php @@ -16,8 +16,8 @@ class Packages implements \IteratorAggregate private $classmapName = 'autoload_classmap.php'; private $psrZeroName = 'autoload_namespaces.php'; private $psrFourName = 'autoload_psr4.php'; + private $libs = array(); private $log = array(); - private $libs; /** * Create a `Inphinit\Packages` instance. @@ -33,7 +33,6 @@ public function __construct($path) } $this->composerPath = str_replace('\\', '/', realpath($path)) . '/'; - $this->libs = new \ArrayIterator(array()); } /** @@ -70,7 +69,7 @@ public function inAutoload() $data = include $path; if (is_array($data)) { - $this->libs = new \ArrayIterator($data + $this->libs->getArrayCopy()); + $this->libs = $data + $this->libs; } return count($this->libs); @@ -204,7 +203,7 @@ public function save($path) */ public function getIterator() { - return $this->libs; + return new \ArrayIterator($this->libs); } private static function relativePath($path) diff --git a/src/Inphinit/Routing/Route.php b/src/Inphinit/Routing/Route.php index 8ac42dc..25edade 100644 --- a/src/Inphinit/Routing/Route.php +++ b/src/Inphinit/Routing/Route.php @@ -12,6 +12,7 @@ class Route extends Router { private static $current; + private static $hasParams = false; /** * Register or remove a action from controller for a route @@ -34,6 +35,10 @@ public static function set($method, $path, $action) return null; } + if (strpos($path, '<') !==false) { + self::$hasParams = true; + } + $path = parent::$prefixPath . $path; if (!isset(parent::$httpRoutes[$path])) { @@ -62,7 +67,7 @@ public static function get() if (isset($routes[$path])) { $verbs = $routes[$path]; - } else { + } elseif (self::$hasParams) { foreach ($routes as $route => $actions) { if (parent::find($route, $path, $args)) { $verbs = $actions; diff --git a/src/Utils.php b/src/Utils.php index 3bd0eed..fc37ef1 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -113,12 +113,6 @@ function UtilsPath() */ function UtilsAutoload() { - static $initiate; - - if ($initiate) { - return null; - } - $initiate = true; $prefixes = require INPHINIT_PATH . 'boot/namespaces.php';