diff --git a/composer.json b/composer.json index df87436a6..af316b48d 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ }, "autoload": { "psr-4": { - "Sprout\\": "src/sprout" + "Sprout\\": "src/sprout", + "Sprout\\Core\\": "src/core" }, "files": [ "src/sprout/preboot.php" @@ -46,7 +47,7 @@ "ezyang/htmlpurifier": "^4.17", "giggsey/libphonenumber-for-php": "^8.13", "guzzlehttp/guzzle": "^7.9", - "karmabunny/kb": "^4.61", + "karmabunny/kb": "^4.61.44", "karmabunny/pdb": "^1.6", "karmabunny/rdb": "^1.31", "karmabunny/router": "^2.7.12", diff --git a/composer.lock b/composer.lock index 2a869fe79..b8cdafebc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0053fbec38f3a00097e32e726ba8d68e", + "content-hash": "5d92c33e18206214efdd0869c8138c0c", "packages": [ { "name": "aws/aws-crt-php", @@ -1060,16 +1060,16 @@ }, { "name": "karmabunny/kb", - "version": "v4.61.43", + "version": "v4.61.44", "source": { "type": "git", "url": "https://github.com/Karmabunny/kbphp.git", - "reference": "16bd5d1ddd348a49f86cbe63883a16639b8597d2" + "reference": "9053a50f1d93d8888718578395166b3a4c34de96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Karmabunny/kbphp/zipball/16bd5d1ddd348a49f86cbe63883a16639b8597d2", - "reference": "16bd5d1ddd348a49f86cbe63883a16639b8597d2", + "url": "https://api.github.com/repos/Karmabunny/kbphp/zipball/9053a50f1d93d8888718578395166b3a4c34de96", + "reference": "9053a50f1d93d8888718578395166b3a4c34de96", "shasum": "" }, "require": { @@ -1114,9 +1114,9 @@ ], "support": { "issues": "https://github.com/Karmabunny/kbphp/issues", - "source": "https://github.com/Karmabunny/kbphp/tree/v4.61.43" + "source": "https://github.com/Karmabunny/kbphp/tree/v4.61.44" }, - "time": "2026-02-22T23:27:16+00:00" + "time": "2026-02-24T01:23:48+00:00" }, { "name": "karmabunny/pdb", diff --git a/src/bootstrap.php b/src/bootstrap.php index 6a77c76ce..af86a5db0 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -16,6 +16,7 @@ use Sprout\Exceptions\HttpException; use Sprout\Helpers\Errors; use Sprout\Helpers\I18n; +use Sprout\Helpers\Request; use Sprout\Helpers\Utf8; ini_set('display_errors', '1'); @@ -87,6 +88,11 @@ $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; } +// Hack in query params for CLI scripts. +if (PHP_SAPI === 'cli') { + $_GET = Request::getQueryParams(); +} + require __DIR__ . '/bootstrap/config.php'; Utf8::setup(); @@ -94,6 +100,7 @@ @mkdir(STORAGE_PATH . 'cache', 0755, true); @mkdir(STORAGE_PATH . 'temp', 0755, true); +@mkdir(STORAGE_PATH . 'logs', 0755, true); require __DIR__ . '/bootstrap/kohana.php'; @@ -121,6 +128,10 @@ ini_set('display_errors', Errors::$ENABLE_FATAL_ERRORS ? '0' : '1'); +// TODO make this configurable. +ini_set('error_log', STORAGE_PATH . 'logs/php.log'); +ini_set('log_errors', '1'); + // Now that we have an exception handler - check for pre-execution errors. if (isset($e0)) { throw new ErrorException($e0['message'], 0, $e0['type'], $e0['file'], $e0['line']); @@ -132,6 +143,9 @@ throw new HttpException($status, $error); } +// Welcome system. +require __DIR__ . '/bootstrap/welcome.php'; + // Bootstrap the application. -require APPPATH . 'core/Bootstrap.php'; -return true; \ No newline at end of file +require __DIR__ . '/bootstrap/app.php'; +return true; diff --git a/src/bootstrap/app.php b/src/bootstrap/app.php new file mode 100644 index 000000000..80aa385bf --- /dev/null +++ b/src/bootstrap/app.php @@ -0,0 +1,25 @@ +. + */ + +use Sprout\Core\BaseApp; +use Sprout\Helpers\Config; + +$app = Config::get('config.app'); + +if (!is_subclass_of($app, BaseApp::class)) { + throw new RuntimeException("Application must extend " . BaseApp::class); +} + +// Boot up and go. +$instance = $app::instance(); +$instance->run(); diff --git a/src/bootstrap/kohana.php b/src/bootstrap/kohana.php index 2eadc87a2..f7f9108e8 100644 --- a/src/bootstrap/kohana.php +++ b/src/bootstrap/kohana.php @@ -1,6 +1,11 @@ controller; + Router::$method = $event->method; + Router::$arguments = $event->arguments; +}); + +// Remove the kohana query URI, if present. +if (isset($_GET['kohana_uri'])) { + unset($_GET['kohana_uri']); + $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']); +} diff --git a/src/bootstrap/welcome.php b/src/bootstrap/welcome.php new file mode 100644 index 000000000..f3c32cfc5 --- /dev/null +++ b/src/bootstrap/welcome.php @@ -0,0 +1,22 @@ +. + */ + +use Sprout\Helpers\Modules; +use Sprout\Welcome\WelcomeApp; + +// Mini version of framework when using the welcome system +// that avoids lots of code paths which use a database. +if ($welcome = Modules::getModule('Welcome')) { + $app = WelcomeApp::instance(); + $app->run(); +} diff --git a/src/core/BaseApp.php b/src/core/BaseApp.php new file mode 100644 index 000000000..692374f07 --- /dev/null +++ b/src/core/BaseApp.php @@ -0,0 +1,352 @@ +. + */ + +namespace Sprout\Core; + +use karmabunny\kb\Buffer; +use karmabunny\kb\EventableTrait; +use karmabunny\kb\Events; +use karmabunny\router\Action; +use karmabunny\router\Router; +use Psr\Http\Message\ResponseInterface; +use RuntimeException; +use Sprout\Events\BootstrapEvent; +use Sprout\Events\DisplayEvent; +use Sprout\Events\PostControllerEvent; +use Sprout\Events\PostRoutingEvent; +use Sprout\Events\PreControllerEvent; +use Sprout\Events\PreRoutingEvent; +use Sprout\Events\ShutdownEvent; + +/** + * The base application class. + * + * This performs buffering and routing. + */ +abstract class BaseApp +{ + use EventableTrait; + + /** @var int 1 MiB */ + public static int $SEND_BUFFER_SIZE = 1024 * 1024; + + + /** + * The singleton instance. + * + * @var null|BaseApp + */ + protected static ?BaseApp $_instance = null; + + /** + * The application output buffer. + * + * @var Buffer + */ + protected Buffer $buffer; + + /** + * The application router. + * + * @var Router + */ + protected Router $router; + + /** + * The routes table. + * + * @var array + */ + protected array $routes = []; + + /** + * The base controller class, all controller must extend this. + * + * @var class-string + */ + protected string $controller; + + + public function __construct() + { + $this->buffer = new Buffer(); + $this->router = Router::create([]); + $this->controller = ControllerInterface::class; + } + + + /** + * Initializes the app. + * + * This only runs once. + * + * @return void + * @throws RuntimeException + */ + protected function init() + { + // Process output buffering on shutdown. + Events::on(self::class, ShutdownEvent::class, function() { + $event = new DisplayEvent(); + $event->output = $this->buffer->clean(); + Events::trigger(self::class, $event); + echo $event->output; + }); + } + + + /** + * Returns the singleton instance of the app. + * + * @return BaseApp + * @throws RuntimeException + */ + public static function instance(): BaseApp + { + if (static::$_instance === null) { + // @phpstan-ignore-next-line + $app = new static(); + $app->init(); + + if (!is_subclass_of($app->controller, ControllerInterface::class)) { + throw new RuntimeException("Controller class '{$app->controller}' must extend " . ControllerInterface::class); + } + + static::$_instance = $app; + } + + return static::$_instance; + } + + + /** + * Run the application. + * + * This executes controller instance and runs shutdown events. + * + * @return never + */ + public function run() + { + $this->buffer->start(); + + // Send default text/html UTF-8 header + header('Content-Type: text/html; charset=UTF-8'); + + $event = new BootstrapEvent([ + 'routes' => $this->routes, + ]); + Events::trigger(self::class, $event); + + $this->routes = $event->routes; + $this->router->load($this->routes); + + // Begin. + [$method, $uri] = $this->resolveRequest(); + + $event = new PreRoutingEvent([ + 'method' => $method, + 'uri' => $uri, + ]); + Events::trigger(self::class, $event); + + $method = $event->method; + $uri = $event->uri; + + // Perform routing. + $action = $this->resolveAction($method, $uri); + + $event = new PostRoutingEvent([ + 'method' => $method, + 'uri' => $uri, + 'action' => $action, + ]); + Events::trigger(self::class, $event); + + $action = $event->action; + + // No action raises 404. + if (!$action) { + $this->notFound(); + } + + // Invalid targets also raise a 404. + if (!$action->isController($this->controller)) { + $this->notFound(); + } + + /** @var class-string $controller */ + [$controller, $method] = $action->target; + $arguments = $action->args; + + $event = new PreControllerEvent([ + 'controller' => $controller, + 'method' => $method, + 'arguments' => $arguments, + ]); + Events::trigger(self::class, $event); + + // Execute the request. + $instance = new $controller(); + $response = $instance->_run($method, $arguments); + + // Controller method has been executed + $event = new PostControllerEvent([ + 'response' => $response, + ]); + Events::trigger(self::class, $event); + + $response = $event->response; + + // Send the response. + if ($response instanceof ResponseInterface) { + self::send($response); + } + + // Tie up. + $this->shutdown(); + } + + /** + * Get the incoming request method and URI. + * + * @return array{0:string,1:string} [method, uri] + */ + public function resolveRequest(): array + { + $method = $_SERVER['REQUEST_METHOD']; + $uri = strtok($_SERVER['REQUEST_URI'], '?'); + $uri = trim($uri, '/'); + + return [$method, $uri]; + } + + + /** + * Resolve an action from a method and URI. + * + * @param string $method + * @param string $uri + * @return Action|null + */ + public function resolveAction(string $method, string $uri) + { + return $this->router->find($method, $uri); + } + + + /** + * Trigger the shutdown event and exit. + * + * Use this to safely exit the application and ensure all system resources + * are cleaned up. + * + * @param string|null $message + * @return never + */ + public function shutdown(?string $message = null) + { + Events::trigger(self::class, new ShutdownEvent()); + $this->buffer->end(true); + exit($message ?? 0); + } + + + /** + * Exit with a 404 status. + * + * @return never + */ + public function notFound() + { + if (!headers_sent()) { + http_response_code(404); + } + + $this->shutdown(); + } + + + /** + * Send a response to the client. + * + * @param ResponseInterface $response + * @return void + */ + public static function send(ResponseInterface $response) + { + $version = $response->getProtocolVersion(); + $reason = $response->getReasonPhrase(); + $status = $response->getStatusCode(); + + header("HTTP/{$version} {$status} {$reason}", true, $status); + + foreach ($response->getHeaders() as $name => $values) { + $name = ucwords(strtolower($name), '-'); + $value = implode(', ', $values); + header("{$name}: {$value}", true); + } + + $stream = $response->getBody(); + + if ($stream->isReadable()) { + while (!$stream->eof()) { + echo $stream->read(static::$SEND_BUFFER_SIZE); + } + } + } + + + /** + * Get the buffer instance. + * + * @return Buffer + */ + public function getBuffer(): Buffer + { + return $this->buffer; + } + + + /** + * Get the routes tables. + * + * @return array + */ + public function getRoutes(): array + { + return $this->router->getRoutes(); + } + + + /** + * Flush or discard the application buffer. + * + * @param bool $flush + * @return bool + */ + public static function closeBuffers($flush = true) + { + if (!static::$_instance) { + return false; + } + + if ($flush) { + static::$_instance->getBuffer()->flush(); + } else { + static::$_instance->getBuffer()->discard(); + } + + return true; + } +} diff --git a/src/core/ControllerInterface.php b/src/core/ControllerInterface.php new file mode 100644 index 000000000..2a3b5a868 --- /dev/null +++ b/src/core/ControllerInterface.php @@ -0,0 +1,32 @@ +. + */ + +namespace Sprout\Core; + +/** + * A controller interface. + */ +interface ControllerInterface +{ + + /** + * The application will invoke this method to invoke an action. + * + * If you please, you may wrap this method to create before/after hooks. + * + * @param mixed $method + * @param mixed $args + * @return mixed + */ + public function _run($method, $args); +} diff --git a/src/sprout/App.php b/src/sprout/App.php new file mode 100644 index 000000000..55686f0ea --- /dev/null +++ b/src/sprout/App.php @@ -0,0 +1,147 @@ +. + */ + +namespace Sprout; + +use karmabunny\kb\Events; +use karmabunny\router\Action; +use karmabunny\router\Router; +use Sprout\Controllers\BaseController; +use Sprout\Core\BaseApp; +use Sprout\Events\BootstrapEvent; +use Sprout\Events\DisplayEvent; +use Sprout\Events\NotFoundEvent; +use Sprout\Events\PostRoutingEvent; +use Sprout\Events\PreRoutingEvent; +use Sprout\Exceptions\HttpException; +use Sprout\Helpers\Config; +use Sprout\Helpers\CoreAdminAuth; +use Sprout\Helpers\Modules; +use Sprout\Helpers\Needs; +use Sprout\Helpers\PageRouting; +use Sprout\Helpers\Request; +use Sprout\Helpers\Services; +use Sprout\Helpers\SessionStats; +use Sprout\Helpers\SubsiteSelector; + +/** + * The Sprout application. + */ +class App extends BaseApp +{ + + /** @inheritdoc */ + protected function init() + { + parent::init(); + + $config = Config::get('config.router'); + $this->router = Router::create($config); + $this->routes = Config::get('routes'); + + $this->controller = BaseController::class; + + // Page routing + display handling. + $this->on(PreRoutingEvent::class, [PageRouting::class, 'prerouting']); + $this->on(PostRoutingEvent::class, [PageRouting::class, 'postrouting']); + $this->on(DisplayEvent::class, [Needs::class, 'replacePlaceholders']); + + if (!IN_PRODUCTION AND PHP_SAPI !== 'cli') { + $this->on(BootstrapEvent::class, function() { + header('x-sprout-tag:' . SPROUT_REQUEST_TAG); + }); + } + + Services::register(CoreAdminAuth::class); + + // Initialise all modules. + Modules::loadModules('sprout'); + + // Choose the subsite to use, based on domain, directory, mobile etc. + SubsiteSelector::selectSubsite(); + + SessionStats::init(); + + // Initialise Sprout core code. + require APPPATH . '/sprout_load.php'; + + // Initialise any custom non-module code + if (is_readable(DOCROOT . '/skin/sprout_load.php')) { + require DOCROOT . '/skin/sprout_load.php'; + } + + Services::lock(); + } + + + /** @inheritdoc */ + public function resolveRequest(): array + { + $method = Request::method(); + $uri = Request::findUri(); + return [$method, $uri]; + } + + + /** @inheritdoc */ + public function resolveAction(string $method, string $uri): ?Action + { + if ($uri === '') { + $uri = '_default'; + } + + $action = parent::resolveAction($method, $uri); + + // Convert regex syntax into target + args. + if ( + $action !== null + and is_string($action->target) + and preg_match('!^([^/]+)/(.*)$!', $action->target, $matches) + ) { + [, $class, $arguments] = $matches; + + // Compat. + if (strpos($class, '\\') === false) { + $class = 'Sprout\\Controllers\\' . $class; + } + + if (strpos($arguments, '$') !== false) { + $arguments = preg_replace('#^' . $action->rule . '$#u', $arguments, $action->path); + } + + $arguments = explode('/', $arguments); + $method = array_shift($arguments); + + // Rewrite the action target + args. + $action->target = [$class, $method]; + $action->args = $arguments; + } + + return $action; + } + + + /** @inheritdoc */ + public function notFound() + { + $event = new NotFoundEvent(); + Events::trigger(self::class, $event); + + if (!$event->handled) { + throw new HttpException(404); + } + + parent::notFound(); + } + +} diff --git a/src/sprout/Controllers/BaseController.php b/src/sprout/Controllers/BaseController.php index 68c0d21fb..e4b39af98 100644 --- a/src/sprout/Controllers/BaseController.php +++ b/src/sprout/Controllers/BaseController.php @@ -17,19 +17,17 @@ namespace Sprout\Controllers; use BadMethodCallException; -use Exception; use karmabunny\kb\Events; use Kohana; use ReflectionException; use ReflectionMethod; +use Sprout\App; +use Sprout\Core\ControllerInterface; use Sprout\Helpers\ModuleInterface; use Sprout\Helpers\Modules; use Sprout\Events\AfterActionEvent; -use Sprout\Events\NotFoundEvent; use Sprout\Events\BeforeActionEvent; use Sprout\Helpers\Html; -use Sprout\Helpers\Sprout; -use Sprout\Helpers\Text; /** * This is a true base controller. @@ -38,7 +36,7 @@ * * @package Sprout\Controllers */ -abstract class BaseController +abstract class BaseController implements ControllerInterface { // Allow all controllers to run in production by default @@ -58,7 +56,7 @@ public function __construct() /** - * The router/kohana will invoke this method to invoke an action. + * The application will invoke this method to invoke an action. * * If you please, you may wrap this method to create before/after hooks. * @@ -71,6 +69,10 @@ public function _run($method, $args) try { $reflect = new ReflectionMethod($this, $method); + if (!static::ALLOW_PRODUCTION and IN_PRODUCTION) { + throw new ReflectionException('controller not allowed in production'); + } + // Do not allow access to hidden methods if ($method[0] === '_') { throw new ReflectionException('hidden controller method'); @@ -82,9 +84,7 @@ public function _run($method, $args) } } catch (ReflectionException $exception) { - $event = new NotFoundEvent(); - Events::trigger(Kohana::class, $event); - return; + App::instance()->notFound(); } $event = new BeforeActionEvent([ @@ -93,7 +93,7 @@ public function _run($method, $args) 'arguments' => $args, ]); - Events::trigger(BaseController::class, $event); + Events::trigger(self::class, $event); if ($event->cancelled) { return null; @@ -102,7 +102,7 @@ public function _run($method, $args) $response = $this->$method(...$args); $event = new AfterActionEvent(['result' => $response]); - Events::trigger(BaseController::class, $event); + Events::trigger(self::class, $event); $response = $event->result; return $response; diff --git a/src/sprout/Controllers/DbToolsController.php b/src/sprout/Controllers/DbToolsController.php index f2ae3d5d9..268375ad7 100644 --- a/src/sprout/Controllers/DbToolsController.php +++ b/src/sprout/Controllers/DbToolsController.php @@ -33,6 +33,7 @@ use karmabunny\pdb\PdbParser; use Sprout\Events\DisplayEvent; use PDOStatement; +use Sprout\App; use Sprout\Exceptions\ValidationException; use Sprout\Exceptions\WorkerJobException; use Sprout\Helpers\Admin; @@ -3169,7 +3170,7 @@ public function varDump() echo '

This will not include display/shutdown events, for (obvious) reasons.

'; echo '
!!EVENT_DUMP!!
'; - Events::on(Kohana::class, function(DisplayEvent $event) { + Events::on(App::class, function(DisplayEvent $event) { $log = print_r(Events::getLogs(['flatten' => true]), true); $event->output = str_replace('!!EVENT_DUMP!!', $log, $event->output); }); diff --git a/src/sprout/Events/BootstrapEvent.php b/src/sprout/Events/BootstrapEvent.php new file mode 100644 index 000000000..7642a9100 --- /dev/null +++ b/src/sprout/Events/BootstrapEvent.php @@ -0,0 +1,31 @@ +. + */ + +namespace Sprout\Events; + +use karmabunny\kb\Event; + +/** + * An event triggered at the very start of the application. + * + * This is after the output bufferring is active and before routes are loaded. + */ +class BootstrapEvent extends Event +{ + /** + * The route table. + * + * @var array + */ + public $routes = []; +} diff --git a/src/sprout/Events/PostControllerEvent.php b/src/sprout/Events/PostControllerEvent.php index 8a38117a7..ba7efb13c 100644 --- a/src/sprout/Events/PostControllerEvent.php +++ b/src/sprout/Events/PostControllerEvent.php @@ -17,4 +17,10 @@ class PostControllerEvent extends Event { + /** + * The result from the controller method. + * + * @var mixed + */ + public $response; } diff --git a/src/sprout/Events/PostRoutingEvent.php b/src/sprout/Events/PostRoutingEvent.php index a26db0570..48f908bb8 100644 --- a/src/sprout/Events/PostRoutingEvent.php +++ b/src/sprout/Events/PostRoutingEvent.php @@ -14,13 +14,28 @@ namespace Sprout\Events; use karmabunny\kb\Event; +use karmabunny\router\Action; class PostRoutingEvent extends Event { + /** + * The request method. + * + * @var string + */ + public $method; + /** * Current URI without leading or trailing slash. * * @var string */ public $uri; + + /** + * The action to execute. + * + * @var Action|null + */ + public $action; } diff --git a/src/sprout/Events/PreControllerEvent.php b/src/sprout/Events/PreControllerEvent.php index 79fd795ae..d9ad19188 100644 --- a/src/sprout/Events/PreControllerEvent.php +++ b/src/sprout/Events/PreControllerEvent.php @@ -17,4 +17,22 @@ class PreControllerEvent extends Event { + /** + * The controller class. + * + * @var string + */ + public $controller; + + /** + * The method to call on the controller. + */ + public $method; + + /** + * The arguments to pass to the controller method. + * + * @var array + */ + public $arguments; } diff --git a/src/sprout/Events/PreRoutingEvent.php b/src/sprout/Events/PreRoutingEvent.php index 658d8c1ec..52c7167dd 100644 --- a/src/sprout/Events/PreRoutingEvent.php +++ b/src/sprout/Events/PreRoutingEvent.php @@ -17,6 +17,14 @@ class PreRoutingEvent extends Event { + + /** + * The request method. + * + * @var string + */ + public $method; + /** * Current URI without leading or trailing slash. * diff --git a/src/sprout/Events/SendHeadersEvent.php b/src/sprout/Events/SendHeadersEvent.php index e1608ce67..01f986088 100644 --- a/src/sprout/Events/SendHeadersEvent.php +++ b/src/sprout/Events/SendHeadersEvent.php @@ -15,6 +15,10 @@ use karmabunny\kb\Event; +/** + * @deprecated use headers_sent() + * @package Sprout\Events + */ class SendHeadersEvent extends Event { } diff --git a/src/sprout/Helpers/CliServer.php b/src/sprout/Helpers/CliServer.php index 8545193cd..efe366bf6 100644 --- a/src/sprout/Helpers/CliServer.php +++ b/src/sprout/Helpers/CliServer.php @@ -77,7 +77,8 @@ public static function serve(): bool // Kohana bootstrap hasn't happened yet, so Request/Router are // empty at this point. Gotta parse it all ourselves. $url = parse_url($_SERVER['REQUEST_URI']); - $_SERVER['QUERY_STRING'] = $url['query'] ?? ''; + $url['query'] ??= ''; + $path = trim($url['path'], '/'); // Perform rewrites. @@ -88,9 +89,15 @@ public static function serve(): bool return false; } - // Prep a URL for kohana. // Like, if we did any rewrites. - $_GET['kohana_uri'] = $path; + $_SERVER['REQUEST_URI'] = '/' . $path; + $_SERVER['QUERY_STRING'] = $url['query']; + + if ($url['query']) { + $_SERVER['REQUEST_URI'] .= '?' . $url['query']; + } + + Request::findUri(true); return true; } diff --git a/src/sprout/Helpers/Config.php b/src/sprout/Helpers/Config.php index c130e942a..cc6fc6523 100644 --- a/src/sprout/Helpers/Config.php +++ b/src/sprout/Helpers/Config.php @@ -13,422 +13,42 @@ namespace Sprout\Helpers; -use ArrayObject; -use Exception; -use Kohana_Exception; +use karmabunny\kb\Config as BaseConfig; /** * Configuration helper. */ -class Config +class Config extends BaseConfig { - // Configuration - private static $configuration; - - // Include paths - private static $include_paths; - - // Internal cache - private static $internal_cache = []; - - // Cache enabled - public static $cache_enabled = IN_PRODUCTION; - - /** - * Get all include paths. APPPATH is the first path, followed by module - * paths in the order they are configured. + * Search paths for config files. * - * @param bool $process re-process the include paths - * @return array + * @var string[] */ - public static function includePaths($process = FALSE) - { - if ($process === TRUE) - { - self::$include_paths = array(); - - // Sprout modules first - foreach (Modules::getModules() as $module) - { - if ($path = str_replace('\\', '/', $module->getPath())) - { - // Add a valid path - self::$include_paths[] = $path; - } - } + public static array $paths = [ + 'sprout' => APPPATH . 'config/', + 'docroot' => DOCROOT . 'config/', + ]; - // Add Sprout core next - self::$include_paths[] = APPPATH; - } - return self::$include_paths; - } - - - /** - * Get a config item or group. - * - * @param string $key item name - * @param bool $required is the item required? - * @return mixed - */ - public static function get($key, $required = TRUE) + /** @inheritdoc */ + public function getPaths(): array { - if (self::$configuration === NULL) - { - // Load core configuration - self::$configuration = array(); - self::$configuration['config'] = self::load('config'); - - // Re-parse the include paths - self::includePaths(TRUE); - } - - // Get the group name from the key - $group = explode('.', $key, 2); - $group = $group[0]; - - $configuration = self::$configuration; - $sub_config = self::$configuration[$group] ?? null; - - if ($sub_config === null) { - // Load the configuration group - $sub_config = self::load($group, $required); - $configuration[$group] = $sub_config; - - // Store it if we're happy about the subsites. - if ( - $group !== 'sprout' - or !empty(SubsiteSelector::$subsite_code) - ) { - self::$configuration[$group] = $sub_config; - } - } - - // Get the value of the key string - $value = self::keyString($configuration, $key); - - return $value; + return self::$paths; } /** - * Sets a configuration item, if allowed. + * Load a config file, no overrides, no caching. * - * @param string $key config key string - * @param string $value config value - * @return bool - */ - public static function set($key, $value) - { - // Do this to make sure that the config array is already loaded - self::get($key); - - if (substr($key, 0, 7) === 'routes.') - { - // Routes cannot contain sub keys due to possible dots in regex - $keys = explode('.', $key, 2); - } - else - { - // Convert dot-noted key string to an array - $keys = explode('.', $key); - } - - // Used for recursion - $conf =& self::$configuration; - $last = count($keys) - 1; - - foreach ($keys as $i => $k) - { - if ($i === $last) - { - $conf[$k] = $value; - } - else - { - $conf =& $conf[$k]; - } - } - - return TRUE; - } - - - /** - * Load a kohana config file. - * - * This assumes that the file will _declare_ an array called named - * 'config' - or defined by the `$name` parameter. - * - * @param string $file absolute path to file - * @param string $name variable name - * @return array|null + * @param string $file + * @param string $name + * @return array */ public static function include(string $file, string $name = 'config') { - static $__recurse; - - // Prevent infinite recursion. - if ($file === $__recurse) { - throw new Exception('Recursive config file inclusion: ' . basename($file, '.php')); - } - - // TODO should we throw if the file doesn't exist? - - return (function($__file, $__name) use (&$__recurse) { - try { - $__recurse = $__file; - include $__file; - - if (isset($$__name) and is_array($$__name)) { - return $$__name; - } - - return null; - } finally { - $__recurse = null; - } - })($file, $name); - } - - - /** - * Load a config file. - * - * @param string $name config filename, without extension - * @param bool $required is the file required? - * @return array - */ - public static function load($name, $required = TRUE) - { - if ($name === 'config') - { - // Load the application configuration file - $config = self::include(APPPATH . 'config/config.php', 'config'); - - if ( ! isset($config['site_domain'])) - { - // Invalid config file - die('Your Kohana application configuration file is not valid.'); - } - - return $config; - } - - $is_sprout = $name === 'sprout'; - - if ( - !$is_sprout - and self::$cache_enabled - and isset(self::$internal_cache['configuration'][$name]) - ) { - return self::$internal_cache['configuration'][$name]; - } - - // Load matching configs - $configuration = array(); - - if ($files = self::findFile('config', $name, $required)) - { - foreach ($files as $file) - { - $config = self::include($file, 'config'); - - if (isset($config)) - { - // Merge in configuration - $configuration = array_merge($configuration, $config); - } - } - } - - if (!$is_sprout) { - self::$internal_cache['configuration'][$name] = $configuration; - } - - return $configuration; - } - - /** - * Clears a config group from the cached configuration. - * - * @param string $group config group - * @return void - */ - public static function clear($group) - { - // Remove the group from config - unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]); - } - - - - - /** - * Find a resource file in a given directory. Files will be located according - * to the order of the include paths. Configuration and i18n files will be - * returned in reverse order. - * - * @throws Kohana_Exception if file is required and not found - * @param string $directory directory to search in - * @param string $filename filename to look for (without extension) - * @param bool $required file required - * @param string|false $ext file extension - * @return array|string|false - * - array: if the type is config, i18n or l10n - * - string: if the file is found - * - false: if the file is not found - */ - public static function findFile($directory, $filename, $required = FALSE, $ext = FALSE) - { - // NOTE: This test MUST be not be a strict comparison (===), or empty - // extensions will be allowed! - if ($ext == '') - { - // Use the default extension - $ext = '.php'; - } - else - { - // Add a period before the extension - $ext = '.'.$ext; - } - - // Search path - $search = $directory.'/'.$filename.$ext; - $is_sprout = strpos($search, 'config/sprout') === 0; - - if ( - !$is_sprout - and self::$cache_enabled - and isset(self::$internal_cache['find_file_paths'][$search]) - ) { - return self::$internal_cache['find_file_paths'][$search]; - } - - // Load include paths - $paths = self::$include_paths; - - // Nothing found, yet - $found = NULL; - - if ($directory === 'config') - { - array_unshift($paths, DOCROOT); - array_unshift($paths, DOCROOT . 'skin/' . SubsiteSelector::$subsite_code . '/'); - } - else if ($directory === 'views') - { - array_unshift($paths, DOCROOT . 'skin/' . SubsiteSelector::$subsite_code . '/'); - } - - if ($directory === 'config' OR $directory === 'i18n') - { - // Search in reverse, for merging - $paths = array_reverse($paths); - - foreach ($paths as $path) - { - if (is_file($path.$search)) - { - // A matching file has been found - $found[] = $path.$search; - } - } - } - else - { - foreach ($paths as $path) - { - if (is_file($path.$search)) - { - // A matching file has been found - $found = $path.$search; - - // Stop searching - break; - } - } - } - - if ($found === NULL) - { - if ($required === TRUE) - { - // Directory i18n key - $directory = 'core.'.Inflector::singular($directory); - - // If the file is required, throw an exception - throw new Kohana_Exception('core.resource_not_found', I18n::lang($directory), $filename); - } - else - { - // Nothing was found, return FALSE - $found = FALSE; - } - } - - if (!$is_sprout) { - self::$internal_cache['find_file_paths'][$search] = $found; - } - - return $found; - } - - /** - * Lists all files and directories in a resource path. - * - * @param string $directory directory to search - * @param bool $recursive list all files to the maximum depth? - * @param string|false $path full path to search (used for recursion, *never* set this manually) - * @return array filenames and directories - */ - public static function listFiles($directory, $recursive = FALSE, $path = FALSE) - { - $files = array(); - - if ($path === FALSE) - { - $paths = array_reverse(self::includePaths()); - - foreach ($paths as $path) - { - // Recursively get and merge all files - $files = array_merge($files, self::listFiles($directory, $recursive, $path.$directory)); - } - } - else - { - $path = rtrim($path, '/').'/'; - - if (is_readable($path)) - { - $items = (array) glob($path.'*'); - - if ( ! empty($items)) - { - foreach ($items as $index => $item) - { - $files[] = $item = str_replace('\\', '/', $item); - - // Handle recursion - if (is_dir($item) AND $recursive == TRUE) - { - // Filename should only be the basename - $item = pathinfo($item, PATHINFO_BASENAME); - - // Append sub-directory search - $files = array_merge($files, self::listFiles($directory, TRUE, $path.$item)); - } - } - } - } - } - - return $files; + return self::load($file, $name); } @@ -438,121 +58,25 @@ public static function listFiles($directory, $recursive = FALSE, $path = FALSE) * @param array $array array to search * @param string $keys dot-noted string: foo.bar.baz * @return string|array|null + * @deprecated use Config::query() instead */ - public static function keyString($array, $keys) + public static function keyString(array $array, string $keys) { - if (empty($array)) - return NULL; - - // Prepare for loop - $keys = explode('.', $keys); - - if (count($keys) == 2) - { - return @$array[$keys[0]][$keys[1]]; - } - - do - { - // Get the next key - $key = array_shift($keys); - - if (isset($array[$key])) - { - if (is_array($array[$key]) AND ! empty($keys)) - { - // Dig down to prepare the next loop - $array = $array[$key]; - } - else - { - // Requested key was found - return $array[$key]; - } - } - else - { - // Requested key is not set - break; - } - } - // @phpstan-ignore-next-line: array_shift() will eventually empty the array. - while ( ! empty($keys)); - - return NULL; + return self::query($array, $keys); } + /** * Sets values in an array by using a 'dot-noted' string. * - * @param array|object $array array to set keys in (reference) + * @param array $array array to set keys in (reference) * @param string $keys dot-noted string: foo.bar.baz - * @param mixed $fill fill value for the key + * @param mixed $value fill value for the key * @return void + * @deprecated use Config::querySet() instead */ - public static function keyStringSet( & $array, $keys, $fill = NULL) + public static function keyStringSet(array &$array, string $keys, $value) { - if (is_object($array) AND ($array instanceof ArrayObject)) - { - // Copy the array - $array_copy = $array->getArrayCopy(); - - // Is an object - $array_object = TRUE; - } - else - { - if ( ! is_array($array)) - { - // Must always be an array - $array = (array) $array; - } - - // Copy is a reference to the array - $array_copy =& $array; - } - - if (empty($keys)) - return; - - // Create keys - $keys = explode('.', $keys); - - // Create reference to the array - $row =& $array_copy; - - for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++) - { - // Get the current key - $key = $keys[$i]; - - if ( ! isset($row[$key])) - { - if (isset($keys[$i + 1])) - { - // Make the value an array - $row[$key] = array(); - } - else - { - // Add the fill key - $row[$key] = $fill; - } - } - elseif (isset($keys[$i + 1])) - { - // Make the value an array - $row[$key] = (array) $row[$key]; - } - - // Go down a level, creating a new row reference - $row =& $row[$key]; - } - - if (isset($array_object)) - { - // Swap the array back in - $array->exchangeArray($array_copy); - } + self::querySet($array, $keys, $value); } } diff --git a/src/sprout/Helpers/Errors.php b/src/sprout/Helpers/Errors.php index 05616743e..ff0e9ba20 100644 --- a/src/sprout/Helpers/Errors.php +++ b/src/sprout/Helpers/Errors.php @@ -24,6 +24,7 @@ use Kohana_Exception; use PDOStatement; use ReflectionClass; +use Sprout\App; use Sprout\Events\ErrorEvent; use Sprout\Events\ShutdownEvent; use Sprout\Exceptions\HttpException; @@ -525,9 +526,7 @@ public static function exceptionHandler(Throwable $exception) } // Close all output buffers, except our own. - while (ob_get_level() > Kohana::$buffer_level) { - ob_end_clean(); - } + App::closeBuffers(false); // Send the headers if they have not already been sent if ($exception instanceof HttpException and !headers_sent()) { @@ -620,15 +619,12 @@ public static function exceptionHandler(Throwable $exception) } } - // Run the shutdown even to ensure a clean exit - if (!Events::hasRun(Kohana::class, ShutdownEvent::class)) { - $event = new ShutdownEvent(); - Events::trigger(Kohana::class, $event); - } - // Turn off error reporting - error_reporting(0); - exit; + Events::on(App::class, ShutdownEvent::class, function() { + error_reporting(0); + }); + + App::instance()->shutdown(); } catch (Throwable $e) { diff --git a/src/sprout/Helpers/Modules.php b/src/sprout/Helpers/Modules.php index 62e0221a9..35d238dd2 100644 --- a/src/sprout/Helpers/Modules.php +++ b/src/sprout/Helpers/Modules.php @@ -64,6 +64,8 @@ public static function register(string $module) $instance = new $module(); self::$modules[$name] = $instance; + Config::$paths[$name] = $instance->getPath() . 'config/'; + return $instance; } diff --git a/src/sprout/Helpers/PageRouting.php b/src/sprout/Helpers/PageRouting.php index 0ada4d079..37419b8a9 100644 --- a/src/sprout/Helpers/PageRouting.php +++ b/src/sprout/Helpers/PageRouting.php @@ -14,7 +14,10 @@ namespace Sprout\Helpers; use karmabunny\pdb\Exceptions\QueryException; - +use karmabunny\router\Action; +use Sprout\Controllers\PageController; +use Sprout\Events\PostRoutingEvent; +use Sprout\Events\PreRoutingEvent; /** * Logic for selecting the page if no controller matches @@ -25,15 +28,15 @@ class PageRouting /** * Called before the main Kohana routing code **/ - public static function prerouting() + public static function prerouting(PreRoutingEvent $event) { - if (strpos(Router::$current_uri, 'admin/') === 0) return; - if (strpos(Router::$current_uri, 'dbtools/') === 0) return; - if (strpos(Router::$current_uri, '_media/') === 0) return; + if (strpos($event->uri, 'admin/') === 0) return; + if (strpos($event->uri, 'dbtools/') === 0) return; + if (strpos($event->uri, '_media/') === 0) return; // Redirect try { - $url_std = trim(Router::$current_uri, '/ '); + $url_std = trim($event->uri, '/ '); $params = [ 'url_std' => $url_std, 'url_like' => Pdb::likeEscape($url_std), @@ -84,33 +87,38 @@ public static function prerouting() /** * Called after the main Kohana routing code **/ - public static function postrouting() + public static function postrouting(PostRoutingEvent $event) { // This should have already hit a controller or produced a config error. - if (Router::$current_uri === '') { + if ($event->uri === '') { return; } - // If we've already got a controller, there isn't anything to do here - if (Router::$controller !== NULL) { + // If we've already got an action, there isn't anything to do here + if ($event->action) { return; } - Router::$controller = 'Sprout\\Controllers\\PageController'; + $event->action = new Action([ + 'method' => $event->method, + 'path' => '/' . $event->uri, + 'rule' => '-generated-', + 'target' => [ + PageController::class, + 'fourOhFour', + ], + 'args' => [$event->uri], + ]); // Look for a valid page $root = Navigation::getRootNode(); - $matcher = new TreenodePathMatcher(Router::$current_uri); + $matcher = new TreenodePathMatcher($event->uri); $node = $root->findNode($matcher); + if ($node) { - Router::$method = 'viewById'; - Router::$arguments = array($node['id']); - return; + $event->action->target[1] = 'viewById'; + $event->action->args = [$node['id']]; } - - // 404 error - Router::$method = 'fourOhFour'; - Router::$arguments = array(Router::$current_uri); } } diff --git a/src/sprout/Helpers/Request.php b/src/sprout/Helpers/Request.php index acc18028c..689b25556 100644 --- a/src/sprout/Helpers/Request.php +++ b/src/sprout/Helpers/Request.php @@ -132,6 +132,51 @@ public static function isIframe(): bool } + /** + * Get the incoming request URI. + * + * @param bool $refresh + * @return string + */ + public static function findUri(bool $refresh = false): string + { + static $uri = null; + + if ($uri !== null and !$refresh) { + return $uri; + } + + if (PHP_SAPI === 'cli') { + $uri = $_SERVER['argv'][1] ?? ''; + + } else if (isset($_SERVER['REQUEST_URI'])) { + // Everyone should be using this. + $uri = $_SERVER['REQUEST_URI']; + $uri = preg_replace('!^https?://[^/]+!i', '', $uri); + + if (($pos = strpos($uri, '?')) !== false) { + $uri = substr($uri, 0, $pos); + } + + } else if (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO']) { + // This is IIS, not that we support it in any other way. + $uri = $_SERVER['ORIG_PATH_INFO']; + + } else if (isset($_GET['kohana_uri'])) { + // A last resort, but really shouldn't need it. + $uri = $_GET['kohana_uri']; + + } else { + // This is often just garbage. + $uri = $_SERVER['PATH_INFO'] ?? ''; + } + + $uri = trim($uri, '/'); + + return $uri; + } + + /** * Returns current request method. * @@ -604,11 +649,32 @@ public static function getParam(string $name) * * This is the same as $_GET. * + * Or when running as a CLI script the query is parsed from the first argument. + * * @return array */ public static function getQueryParams(): array { - return $_GET; + if (PHP_SAPI === 'cli') { + static $params = null; + + if ($params !== null) { + return $params; + } + + $uri = $_SERVER['argv'][1] ?? ''; + $index = strpos($uri, '?'); + $params = []; + + if ($index !== false) { + $query = substr($uri, $index + 1); + parse_str($query, $params); + } + + return $params; + } else { + return $_GET; + } } @@ -627,15 +693,20 @@ public static function getQueryParam(string $name) /** * The search query. * - * Should be the same as Router::$query_string. + * This is constructed from the $_GET. * + * @param bool $original use the query string as originally passed in the request * @return string */ - public static function getQueryString(): string + public static function getQueryString(bool $original = false): string { - $query = self::getQueryParams(); - $query = http_build_query($query); - return $query; + if ($original) { + return $_SERVER['QUERY_STRING'] ?? ''; + } else { + $query = self::getQueryParams(); + $query = http_build_query($query); + return $query; + } } diff --git a/src/sprout/Helpers/Router.php b/src/sprout/Helpers/Router.php index eaed214a4..87e0ab2d2 100644 --- a/src/sprout/Helpers/Router.php +++ b/src/sprout/Helpers/Router.php @@ -15,15 +15,7 @@ */ namespace Sprout\Helpers; -use Exception; -use karmabunny\kb\Events; -use Kohana; -use Kohana_Exception; -use utf8; - -use karmabunny\router\Router as KbRouter; -use Sprout\Events\PostRoutingEvent; -use Sprout\Events\PreRoutingEvent; +use Sprout\App; /** * Router @@ -39,226 +31,31 @@ class Router /** Original URI and query string combined. */ public static $complete_uri = ''; - /** Controller/method URI to use, from the configured routes */ + /** @deprecated always empty */ public static $routed_uri = ''; - /** Optional fake file extension that will be added to all generated URLs, e.g. '.html' */ + /** @deprecated always empty */ public static $url_suffix = ''; - /** Controller to use */ + /** @deprecated use PostRoutingEvent */ public static $controller; - /** Method to call on controller */ + /** @deprecated use PostRoutingEvent */ public static $method = 'index'; - /** Arguments to pass to controller method */ + /** @deprecated use PostRoutingEvent */ public static $arguments = []; - /** @var KbRouter */ - protected static $router; - - - /** - * Router setup routine; determines controller/method from URI. - * Automatically called during Kohana setup process. - * - * @return void - */ - public static function setup() - { - $event = new PreRoutingEvent([ - 'uri' => trim(Router::$current_uri, '/'), - ]); - - Events::trigger(Router::class, $event); - - // Load configured routes - $routes = Kohana::config('routes'); - - // Use the default route when no segments exist - $uri = self::$current_uri; - if ($uri === '') { - if (!isset($routes['_default'])) { - throw new Kohana_Exception('core.no_default_route'); - } - - $uri = '_default'; - } - - $config = Kohana::config('core.router'); - - self::$router = KbRouter::create($config); - self::$router->load($routes); - - // Find matching configured route - $routed_uri = Router::routedUri($uri); - - // The routed URI is now complete - if ($routed_uri !== false) { - Router::$routed_uri = $routed_uri; - - // Find the controller from the registered route. If no namespace specified, assume Sprout\Controllers\... - $segments = explode('/', trim(Router::$routed_uri, '/')); - $controller = array_shift($segments); - if (strpos($controller, '\\') === false) $controller = 'Sprout\\Controllers\\' . $controller; - if (class_exists($controller)) { - Router::$controller = $controller; - if (count($segments) > 0) { - Router::$method = array_shift($segments); - Router::$arguments = $segments; - } else { - Router::$arguments = []; - } - } - } - - $event = new PostRoutingEvent([ - 'uri' => trim($uri, '/'), - ]); - - Events::trigger(Router::class, $event); - } - /** * Get the routes tables. * + * @deprecated use App::instance()->getRoutes() instead. * @return array */ public static function getRoutes() { - return self::$router->routes; - } - - - /** - * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF. - * - * @return void - */ - public static function findUri() - { - if (PHP_SAPI === 'cli') - { - // Command line requires a bit of hacking - if (isset($_SERVER['argv'][1])) - { - Router::$current_uri = $_SERVER['argv'][1]; - - // Remove GET string from segments - if (($query = strpos(Router::$current_uri, '?')) !== FALSE) - { - list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2); - - // Parse the query string into $_GET - parse_str($query, $_GET); - - // Convert $_GET to UTF-8 - $_GET = utf8::clean($_GET); - } - } - } - elseif (isset($_GET['kohana_uri'])) - { - // Use the URI defined in the query string - Router::$current_uri = $_GET['kohana_uri']; - - // Remove the URI from $_GET - unset($_GET['kohana_uri']); - - // Remove the URI from $_SERVER['QUERY_STRING'] - $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']); - } - elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO']) - { - Router::$current_uri = $_SERVER['PATH_INFO']; - } - elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO']) - { - Router::$current_uri = $_SERVER['ORIG_PATH_INFO']; - } - elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF']) - { - Router::$current_uri = $_SERVER['PHP_SELF']; - } - - if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE) - { - // Remove the front controller from the current uri - Router::$current_uri = (string) substr(Router::$current_uri, $strpos_fc + strlen(KOHANA)); - } - - // Remove slashes from the start and end of the URI - Router::$current_uri = trim(Router::$current_uri, '/'); - - if (Router::$current_uri !== '') - { - if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE) - { - // Remove the URL suffix - Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri); - - // Set the URL suffix - Router::$url_suffix = $suffix; - } - - // Reduce multiple slashes into single slashes - Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri); - } - - // Set the query string to the current query string - if (!empty($_SERVER['QUERY_STRING'])) { - Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/'); - } - - // Remove all dot-paths from the URI, they are not valid - Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri); - Router::$current_uri = trim(Router::$current_uri, '/'); - - // Remember the complete URI for some reason - Router::$complete_uri = Router::$current_uri . Router::$query_string; - } - - - /** - * Generates routed URI (i.e. controller/method/arg1/arg2/...) from given URI. - * - * @param string $uri URI to convert, e.g. 'admin/edit/page/3' - * @return string|bool Routed URI or false, e.g. 'AdminController/edit/page/3' - * @throws Exception if no routes configured - */ - public static function routedUri($uri) - { - if (Router::$router === NULL or empty(Router::$router->routes)) { - throw new Exception('No routes loaded'); - } - - $method = Request::method(); - $action = self::$router->find(strtoupper($method), $uri); - if (!$action) return false; - - $target = $action->target; - $rule = explode(' ', $action->rule, 2); - $rule = count($rule) == 2 ? $rule[1] : $rule[0]; - - // Convert class::method into sprout style segments. - if (is_array($target)) { - [$class, $method] = $target; - $routed_uri = "{$class}/{$method}"; - - foreach ($action->args as $value) { - $routed_uri .= '/' . $value; - } - } else { - // - 'rule' is a regex: some/regex/([^/]+)/path/([^/]+) - // - 'target' is a placeholder: ns\\to\\class/method/$1/$2 - // - 'uri' is the actual URI: some/regex/123/path/456 - // The results should look like: - // - ns\\to\\class/method/123/456 - $routed_uri = preg_replace('#^' . $rule . '$#u', $target, $uri); - } - - return trim($routed_uri, '/'); + return App::instance()->getRoutes(); } } // End Router diff --git a/src/sprout/Helpers/Session.php b/src/sprout/Helpers/Session.php index 7a2f648df..e7339c3a3 100644 --- a/src/sprout/Helpers/Session.php +++ b/src/sprout/Helpers/Session.php @@ -19,6 +19,7 @@ use Kohana; use Kohana_Exception; use karmabunny\kb\Events; +use Sprout\App; use Sprout\Events\ShutdownEvent; use Sprout\Helpers\Drivers\SessionDriver; @@ -87,7 +88,7 @@ public function __construct() // Close the session on system shutdown (run before sending the headers), so that // the session cookie(s) can be written. - Events::on(Kohana::class, ShutdownEvent::class, [self::class, 'writeClose']); + Events::on(App::class, ShutdownEvent::class, [self::class, 'writeClose']); // Singleton instance Session::$instance = $this; diff --git a/src/sprout/Helpers/SessionStats.php b/src/sprout/Helpers/SessionStats.php index 58a713bde..0be5e163a 100644 --- a/src/sprout/Helpers/SessionStats.php +++ b/src/sprout/Helpers/SessionStats.php @@ -16,7 +16,7 @@ use DateInterval; use DateTime; use karmabunny\kb\Events; -use Kohana; +use Sprout\App; use Sprout\Events\DisplayEvent; class SessionStats @@ -71,7 +71,7 @@ public static function init() self::trackSession(); self::$do_tracking = true; - Events::on(Kohana::class, DisplayEvent::class, [self::class, 'trackPageView']); + Events::on(App::class, DisplayEvent::class, [self::class, 'trackPageView']); } diff --git a/src/sprout/Helpers/Sprout.php b/src/sprout/Helpers/Sprout.php index 122fc081c..50fbcc772 100644 --- a/src/sprout/Helpers/Sprout.php +++ b/src/sprout/Helpers/Sprout.php @@ -25,6 +25,7 @@ use karmabunny\pdb\Exceptions\QueryException; use Psr\Http\Message\ResponseInterface; use ReflectionException; +use Sprout\App; /** * Useful functions for sprout in general @@ -32,13 +33,6 @@ class Sprout { - /** - * When reading out of a response object, process this many bytes at a time. - * - * @var int 1 MiB - */ - public static $SEND_BUFFER_SIZE = 1024 * 1024; - /** * Determines the file path for a class, usually for autoloading @@ -870,25 +864,7 @@ public static function iterableFirstValue($iter) */ public static function send(ResponseInterface $response) { - $version = $response->getProtocolVersion(); - $reason = $response->getReasonPhrase(); - $status = $response->getStatusCode(); - - header("HTTP/{$version} {$status} {$reason}", true, $status); - - foreach ($response->getHeaders() as $name => $values) { - $name = ucwords(strtolower($name), '-'); - $value = implode(', ', $values); - header("{$name}: {$value}", true); - } - - $stream = $response->getBody(); - - if ($stream->isReadable()) { - while (!$stream->eof()) { - echo $stream->read(static::$SEND_BUFFER_SIZE); - } - } + App::send($response); } diff --git a/src/sprout/Helpers/SubsiteSelector.php b/src/sprout/Helpers/SubsiteSelector.php index 2dabfb772..93e44d44f 100644 --- a/src/sprout/Helpers/SubsiteSelector.php +++ b/src/sprout/Helpers/SubsiteSelector.php @@ -157,5 +157,7 @@ public static function setSubsite(array $site) self::$subsite_code = $site['code']; self::$url_prefix = $directory; self::$mobile = $site['mobile'] ?? false; + + Config::$paths['skin'] = DOCROOT . 'skin/' . $site['code'] . '/config/'; } } diff --git a/src/sprout/Helpers/Url.php b/src/sprout/Helpers/Url.php index 81277b889..ff7474695 100644 --- a/src/sprout/Helpers/Url.php +++ b/src/sprout/Helpers/Url.php @@ -19,8 +19,8 @@ use Kohana; use karmabunny\kb\Events; use LogicException; +use Sprout\App; use Sprout\Events\RedirectEvent; -use Sprout\Events\SendHeadersEvent; use Sprout\Events\ShutdownEvent; /** @@ -171,8 +171,7 @@ public static function merge(array $arguments) */ public static function redirect($uri = '', $method = '302'): never { - if (Events::hasRun(Kohana::class, SendHeadersEvent::class)) { - + if (headers_sent()) { if (!IN_PRODUCTION) { throw new LogicException("Attempting to redirect after headers have been sent."); } @@ -222,7 +221,7 @@ public static function redirect($uri = '', $method = '302'): never // Run the redirect event $event = new RedirectEvent(['uri' => $uri]); - Events::trigger(Kohana::class, $event); + Events::trigger(App::class, $event); $uri = $event->uri; if ($method === 'refresh') @@ -235,15 +234,9 @@ public static function redirect($uri = '', $method = '302'): never header('Location: '.$uri); } - // We are about to exit, so run the send_headers event - $event = new SendHeadersEvent(); - Events::trigger(Kohana::class, $event); - // If using a session driver, the session needs to be explicitly saved - $event = new ShutdownEvent(); - Events::trigger(Kohana::class, $event); - - exit('

'.$method.' - '.$codes[$method].'

'.$output); + $html = "

{$method} - {$codes[$method]}

{$output}"; + App::instance()->shutdown($html); } /** diff --git a/src/sprout/Welcome/Controllers/WelcomeController.php b/src/sprout/Welcome/Controllers/WelcomeController.php index 59dbe6ed7..9f9138563 100644 --- a/src/sprout/Welcome/Controllers/WelcomeController.php +++ b/src/sprout/Welcome/Controllers/WelcomeController.php @@ -20,6 +20,7 @@ use Sprout\Controllers\Controller; use karmabunny\pdb\Pdb as PdbConnection; +use karmabunny\router\Route; use Sprout\Helpers\AdminAuth; use Sprout\Helpers\Auth; use Sprout\Helpers\Constants; @@ -63,6 +64,7 @@ public function redirect() /** * Show a phpinfo() view along with some extra information */ + #[Route('info')] public function phpInfo() { $view = new PhpView('modules/Welcome/phpinfo'); @@ -96,6 +98,7 @@ public function phpInfo() /** * Display the welcome checklist */ + #[Route('checklist')] public function checklist() { unset($_SESSION['database_config']); @@ -159,7 +162,7 @@ private function testDbconf() private function testSuperOp() { try { - $ops = Kohana::config('super_ops.operators'); + $ops = Kohana::config('super_ops.operators') ?? []; } catch (Exception $ex) { $ops = []; } @@ -226,6 +229,7 @@ private function testWelcome() /** * Show a UI for generating a database config */ + #[Route('db_conf_form')] public function dbConfForm() { unset($_SESSION['database_config']); @@ -252,6 +256,7 @@ public function dbConfForm() /** * Ajax method to test the db connection for a given set of params */ + #[Route('POST db_conf_test')] public function dbConfTest() { if (empty($_POST['host'])) Json::out(['result' => 'You must specify a host']); @@ -278,6 +283,7 @@ public function dbConfTest() /** * Display the generated database config */ + #[Route('db_conf_result')] public function dbConfResult() { $_SESSION['db_conf']['field_values'] = Validator::trim($_POST); @@ -340,6 +346,7 @@ private static function genEnvFile(array $data) /** * Run a database sync */ + #[Route('sync')] public function sync() { $sync = new DatabaseSync(true); @@ -376,6 +383,7 @@ public function sync() /** * Show a UI to create a super-operator */ + #[Route('super_op_form')] public function superOperatorForm() { Form::loadFromSession('super_op'); @@ -445,6 +453,7 @@ private static function passwordComplexity($str) * * @return void Redirects */ + #[Route('POST super_op_action')] public function superOperatorAction() { $_SESSION['super_op']['field_values'] = Validator::trim($_POST); @@ -510,6 +519,7 @@ public function superOperatorAction() /** * Show the generated super operator details */ + #[Route('super_op_result')] public function superOperatorResult() { $view = new PhpView('modules/Welcome/super_op_result'); @@ -526,6 +536,7 @@ public function superOperatorResult() /** * Add sample content */ + #[Route('add_sample_action')] public function addSampleAction() { // During development, uncomment this line: diff --git a/src/sprout/Welcome/WelcomeApp.php b/src/sprout/Welcome/WelcomeApp.php new file mode 100644 index 000000000..4d2c2c92c --- /dev/null +++ b/src/sprout/Welcome/WelcomeApp.php @@ -0,0 +1,50 @@ +. + */ + +namespace Sprout\Welcome; + +use karmabunny\router\Router; +use Sprout\Controllers\BaseController; +use Sprout\Core\BaseApp; +use Sprout\Events\DisplayEvent; +use Sprout\Helpers\Config; +use Sprout\Helpers\CoreAdminAuth; +use Sprout\Helpers\Needs; +use Sprout\Helpers\Services; +use Sprout\Helpers\SubsiteSelector; + +/** + * A mini application for the welcome system. + */ +class WelcomeApp extends BaseApp +{ + + /** @inheritdoc */ + protected function init() + { + parent::init(); + + $config = Config::get('config.router'); + $this->router = Router::create($config); + $this->routes = Config::load(__DIR__ . '/config/routes.php'); + + $this->controller = BaseController::class; + + $this->on(DisplayEvent::class, [Needs::class, 'replacePlaceholders']); + + Services::register(CoreAdminAuth::class); + Services::lock(); + + SubsiteSelector::setSubsite(['id' => 1, 'code' => 'default']); + } +} diff --git a/src/sprout/Welcome/config/config.php b/src/sprout/Welcome/config/config.php new file mode 100644 index 000000000..32cb5fe97 --- /dev/null +++ b/src/sprout/Welcome/config/config.php @@ -0,0 +1,6 @@ +. */ -$ns = 'Sprout\Welcome\\Controllers\\'; +use Sprout\Welcome\Controllers\WelcomeController; -// Redirect traffic to the home page into the welcome system -$config['_default'] = $ns . 'WelcomeController/redirect'; - -// Useful tools -$config['welcome/info'] = $ns . 'WelcomeController/phpInfo'; - -// The welcome system UI -$config['welcome/checklist'] = $ns . 'WelcomeController/checklist'; -$config['welcome/run_test/([_a-z]+)'] = $ns . 'WelcomeController/runTest/$1'; -$config['welcome/db_conf_form'] = $ns . 'WelcomeController/dbConfForm'; -$config['welcome/db_conf_test'] = $ns . 'WelcomeController/dbConfTest'; -$config['welcome/db_conf_result'] = $ns . 'WelcomeController/dbConfResult'; -$config['welcome/sync'] = $ns . 'WelcomeController/sync'; -$config['welcome/super_op_form'] = $ns . 'WelcomeController/superOperatorForm'; -$config['welcome/super_op_action'] = $ns . 'WelcomeController/superOperatorAction'; -$config['welcome/super_op_result'] = $ns . 'WelcomeController/superOperatorResult'; -$config['welcome/add_sample_action'] = $ns . 'WelcomeController/addSampleAction'; +$config['welcome'][] = WelcomeController::class; +$config['.*'] = [WelcomeController::class, 'redirect']; diff --git a/src/sprout/config/config.php b/src/sprout/config/config.php index f37bc2b82..7757cc7b5 100644 --- a/src/sprout/config/config.php +++ b/src/sprout/config/config.php @@ -15,15 +15,9 @@ */ /** - * Base config file used by Kohana. Won't need changing usually. - * @package Kohana + * The Sprout application class. */ - -/** - * Some config options are a bit more site-specific, so are stored in the root config directory. - * Modules are also loaded from that file. - */ -require_once DOCROOT . 'config/config.php'; +$config['app'] = \Sprout\App::class; /** * Force a default protocol to be used by the site. If no site_protocol is @@ -70,44 +64,6 @@ $config['index_page'] = ''; } -/** - * Fake file extension that will be added to all generated URLs. Example: .html - */ -$config['url_suffix'] = ''; - -/** - * Length of time of the internal cache in seconds. 0 or FALSE means no caching. - * The internal cache stores file paths and config entries across requests and - * can give significant speed improvements at the expense of delayed updating. - */ -$config['internal_cache'] = FALSE; - -/** - * Internal cache directory. - */ -$config['internal_cache_path'] = STORAGE_PATH . 'cache/'; - -/** - * Enable or disable gzip output compression. This can dramatically decrease - * server bandwidth usage, at the cost of slightly higher CPU usage. Set to - * the compression level (1-9) that you want to use, or FALSE to disable. - * - * Do not enable this option if you are using output compression in php.ini! - */ -$config['output_compression'] = FALSE; - -/** - * Enable or disable global XSS filtering of GET, POST, and SERVER data. This - * option also accepts a string to specify a specific XSS filtering tool. - */ -$config['global_xss_filtering'] = FALSE; - -/** - * Enable or disable displaying of Kohana error pages. This will not affect - * logging. Turning this off will disable ALL error pages. - */ -$config['display_errors'] = TRUE; - /** * Configure the router component. * diff --git a/src/sprout/core/Bootstrap.php b/src/sprout/core/Bootstrap.php deleted file mode 100644 index f2e955487..000000000 --- a/src/sprout/core/Bootstrap.php +++ /dev/null @@ -1,84 +0,0 @@ -. - */ - -/** - * Kohana process control file, loaded by the front controller. - * - * $Id: Bootstrap.php 4409 2009-06-06 00:48:26Z zombor $ - * - * @package Core - * @author Kohana Team - * @copyright (c) 2007 Kohana Team - * @license http://kohanaphp.com/license.html - */ - -use karmabunny\kb\Events; -use Sprout\Events\NotFoundEvent; -use Sprout\Helpers\CoreAdminAuth; -use Sprout\Helpers\Modules; -use Sprout\Helpers\Notification; -use Sprout\Helpers\PageRouting; -use Sprout\Helpers\Register; -use Sprout\Helpers\Router; -use Sprout\Helpers\SubsiteSelector; -use Sprout\Helpers\Sprout; -use Sprout\Helpers\Url; - -// Determine the URI (stored in Router::$current_uri) -Router::findUri(); - -Register::services(CoreAdminAuth::class); - -// Mini verion of framework when using the welcome system -// that avoids lots of code paths which use a database. -if (Sprout::moduleInstalled('Welcome')) { - if (Router::$current_uri === '' or strpos(Router::$current_uri, 'welcome/') === 0) { - SubsiteSelector::selectSubsite(); - Router::setup(); - Kohana::run(); - exit(1); - } -} else { - // If the user has just finished setting up - if (strpos(Router::$current_uri, 'welcome/checklist') === 0) { - Notification::error('Welcome-Module not enabled! Enable it via config file.', 'html'); - Notification::confirm('Or please log in to admin area using the form below.'); - Url::redirect('admin/'); - } -} - -// Initialise Sprout modules, if required -Modules::loadModules('sprout'); - -// Choose the subsite to use, based on domain, directory, mobile etc. -SubsiteSelector::selectSubsite(); - -// Any redirects etc before the Kohana URLs -require APPPATH . '/sprout_load.php'; -PageRouting::prerouting(); - -// Kohana routes and controller/method URLs -// Key vars are Router::$controller and Router::$method -Router::setup(); - -// Postrouting such as page URLs -PageRouting::postrouting(); - -// 404? -if (Router::$controller === NULL) { - $event = new NotFoundEvent(); - Events::trigger(Kohana::class, $event); -} - -// Run the application -Kohana::run(); diff --git a/src/sprout/core/Event.php b/src/sprout/core/Event.php index fbdabe6c9..5f5fb94aa 100644 --- a/src/sprout/core/Event.php +++ b/src/sprout/core/Event.php @@ -13,6 +13,7 @@ use karmabunny\kb\EventInterface; use karmabunny\kb\Events; +use Sprout\App; use Sprout\Events\NotFoundEvent; use Sprout\Events\PostControllerEvent; use Sprout\Events\PostControllerConstructorEvent; @@ -92,7 +93,7 @@ public static function add($name, $callback) $class = self::getEventClass($name); if (is_a($class, DisplayEvent::class, true)) { - Events::on(Kohana::class, function(DisplayEvent $event) use ($callback) { + Events::on(App::class, function(DisplayEvent $event) use ($callback) { Event::$data = &$event->output; $callback(); @@ -106,7 +107,7 @@ public static function add($name, $callback) if (is_a($class, RedirectEvent::class, true)) { - Events::on(Kohana::class, function(RedirectEvent $event) use ($callback) { + Events::on(App::class, function(RedirectEvent $event) use ($callback) { Event::$data = &$event->uri; $callback(); @@ -118,7 +119,7 @@ public static function add($name, $callback) return true; } - Events::on(Kohana::class, $class, $callback); + Events::on(App::class, $class, $callback); return true; } @@ -221,7 +222,7 @@ public static function run($name, & $data = NULL) $event->uri = &$data; } - Events::trigger(Kohana::class, $event); + Events::trigger(App::class, $event); } /** diff --git a/src/sprout/core/Kohana.php b/src/sprout/core/Kohana.php index 2716030e3..d6daa6b9a 100644 --- a/src/sprout/core/Kohana.php +++ b/src/sprout/core/Kohana.php @@ -11,30 +11,16 @@ * For more information, visit . */ -use karmabunny\kb\EventInterface; -use karmabunny\kb\Events; -use karmabunny\kb\Uuid; -use Psr\Http\Message\ResponseInterface; -use Sprout\Controllers\BaseController; -use Sprout\Events\DisplayEvent; -use Sprout\Events\NotFoundEvent; -use Sprout\Events\PostControllerConstructorEvent; -use Sprout\Events\PostControllerEvent; -use Sprout\Events\PreControllerEvent; -use Sprout\Events\SendHeadersEvent; -use Sprout\Events\ShutdownEvent; + use Sprout\Exceptions\HttpException; use Sprout\Helpers\Config; use Sprout\Helpers\Enc; use Sprout\Helpers\I18n; use Sprout\Helpers\Errors; -use Sprout\Helpers\Needs; use Sprout\Helpers\Router; -use Sprout\Helpers\Sprout; use Sprout\Helpers\Request; -use Sprout\Helpers\Services; -use Sprout\Helpers\Session; -use Sprout\Helpers\SessionStats; +use Sprout\App; + /** * Provides Kohana-specific helper functions. This is where the magic happens! @@ -46,161 +32,17 @@ * @copyright (c) 2007-2008 Kohana Team * @license http://kohanaphp.com/license.html */ -final class Kohana { +final class Kohana extends App { // The singleton instance of the controller public static $instance; - // Output buffering level - public static $buffer_level; - - // Will be set to TRUE when an exception is caught - public static $has_error = FALSE; - - // Enable or disable the fatal error handler - public static $enable_fatal_errors = TRUE; - - // The final output that will displayed by Kohana - public static $output = ''; - // The current user agent public static $user_agent; // The current locale public static $locale; - /** - * Sets up the PHP environment. Adds error/exception handling, output - * buffering, and adds an auto-loading method for loading classes. - * - * For security, this function also destroys the $_REQUEST global variable. - * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure. - * @see http://www.php.net/globals - * - * @return void - */ - public static function setup() - { - static $run; - - // This function can only be run once - if ($run === TRUE) - return; - - // Start output buffering - ob_start(array(__CLASS__, 'outputBuffer')); - - // Save buffering level - self::$buffer_level = ob_get_level(); - - // Send default text/html UTF-8 header - header('Content-Type: text/html; charset=UTF-8'); - - // Check the CLI domain has been set - if (! Kohana::config('config.cli_domain')) - { - throw new Exception('Sprout config parameter "config.cli_domain" has not been set. See the sprout development documentation for more info.'); - } - - // Enable Kohana 404 pages - Events::on(Kohana::class, NotFoundEvent::class, [Kohana::class, 'show404']); - - // Enable Kohana output handling - Events::on(Kohana::class, ShutdownEvent::class, [Kohana::class, 'shutdown']); - - Events::on(Kohana::class, DisplayEvent::class, [Needs::class, 'replacePlaceholders']); - - // Setup is complete, prevent it from being run again - $run = TRUE; - } - - /** - * Run the application. - * - * This executes controller instance and runs shutdown events. - * - * @return void - */ - public static function run() - { - self::instance(); - - $event = new ShutdownEvent(); - Events::trigger(Kohana::class, $event); - } - - /** - * Loads the controller and initializes it. Runs the pre_controller, - * post_controller_constructor, and post_controller events. Triggers - * a system.404 event when the route cannot be mapped to a controller. - * - * @return object instance of controller - */ - public static function & instance() - { - if (self::$instance === NULL) - { - if (empty(Router::$controller)) { - $event = new NotFoundEvent(); - Events::trigger(Kohana::class, $event); - die; - } - - try { - // Start validation of the controller - $class = new ReflectionClass(Router::$controller); - } catch (ReflectionException $e) { - // Controller does not exist - $event = new NotFoundEvent(); - Events::trigger(Kohana::class, $event); - die; - } - - if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE)) - { - // Controller is not allowed to run in production - $event = new NotFoundEvent(); - Events::trigger(Kohana::class, $event); - die; - } - - // Initialise any custom non-module code - if (is_readable(DOCROOT . '/skin/sprout_load.php')) { - require DOCROOT . '/skin/sprout_load.php'; - } - - // Prevent further service registrations - Services::lock(); - - // Run system.pre_controller - $event = new PreControllerEvent(); - Events::trigger(Kohana::class, $event); - - // Create a new controller instance - $controller = $class->newInstance(); - - if (!($controller instanceof BaseController)) { - throw new Exception("Class doesn't extend BaseController: " . get_class($controller)); - } - - // Controller constructor has been executed - $event = new PostControllerConstructorEvent(); - Events::trigger(Kohana::class, $event); - - $res = $controller->_run(Router::$method, Router::$arguments); - - if ($res instanceof ResponseInterface) { - Sprout::send($res); - } - - // Controller method has been executed - $event = new PostControllerEvent(); - Events::trigger(Kohana::class, $event); - } - - return self::$instance; - } - /** * Get a config item or group. @@ -274,7 +116,7 @@ public static function configLoad($name, $required = TRUE) $name = 'config'; } - return Config::load($name, $required); + return Config::load($name); } @@ -291,149 +133,6 @@ public static function log($type, $message) } - /** - * Kohana output handler. Called during ob_clean, ob_flush, and their variants. - * - * @param string $output current output buffer - * @return string - */ - public static function outputBuffer($output) - { - // Could be flushing, so send headers first - if (!Events::hasRun(Kohana::class, SendHeadersEvent::class)) { - $event = new SendHeadersEvent(); - Events::trigger(Kohana::class, $event); - } - - self::$output = $output; - - // Set and return the final output - return self::$output; - } - - /** - * Closes all open output buffers, either by flushing or cleaning, and stores the Kohana - * output buffer for display during shutdown. - * - * @param bool $flush disable to clear buffers, rather than flushing - * @return void - */ - public static function closeBuffers($flush = TRUE) - { - if (ob_get_level() >= self::$buffer_level) - { - // Set the close function - $close = ($flush === TRUE) ? 'ob_end_flush' : 'Kohana::_obEndClean'; - - while (ob_get_level() > self::$buffer_level) - { - // Flush or clean the buffer - $close(); - } - - // Store the Kohana output buffer - Kohana::_obEndClean(); - } - } - - /** - * Triggers the shutdown of Kohana by closing the output buffer + running display events. - * - * @return void - */ - public static function shutdown() - { - // Close output buffers - self::closeBuffers(TRUE); - - // Run the output event - $event = new DisplayEvent(['output' => self::$output]); - Events::trigger(Kohana::class, $event); - self::$output = $event->output; - - // Render the final output - self::render(self::$output); - } - - - /** - * Inserts global Kohana variables into the generated output and prints it. - * - * @param string $output final output that will displayed - * @return void - */ - public static function render($output) - { - if ($level = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0) - { - if ($level < 1 OR $level > 9) - { - // Normalize the level to be an integer between 1 and 9. This - // step must be done to prevent gzencode from triggering an error - $level = max(1, min($level, 9)); - } - - if (stripos($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip') !== FALSE) - { - $compress = 'gzip'; - } - elseif (stripos($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'deflate') !== FALSE) - { - $compress = 'deflate'; - } - } - - if (isset($compress) AND $level > 0) - { - switch ($compress) - { - case 'gzip': - // Compress output using gzip - $output = gzencode($output, $level); - break; - case 'deflate': - // Compress output using zlib (HTTP deflate) - $output = gzdeflate($output, $level); - break; - } - - // This header must be sent with compressed content to prevent - // browser caches from breaking - header('Vary: Accept-Encoding'); - - // Send the content encoding header - header('Content-Encoding: '.$compress); - - // Sending Content-Length in CGI can result in unexpected behavior - if (stripos(PHP_SAPI, 'cgi') === FALSE) - { - header('Content-Length: '.strlen($output)); - } - } - - if (!IN_PRODUCTION AND PHP_SAPI !== 'cli') { - header('x-sprout-tag:' . SPROUT_REQUEST_TAG); - } - - echo $output; - } - - /** - * Displays a 404 page. - * - * @param string|false|EventInterface $page URI of page - * @return void - * @throws Kohana_404_Exception - */ - public static function show404($page = FALSE) - { - if ($page instanceof EventInterface) { - $page = false; - } - - throw new Kohana_404_Exception($page); - } - /** * Log exceptions in the database * @@ -516,33 +215,6 @@ public static function backtrace($trace) return Errors::backtrace($trace); } - - /** - * Ends the current output buffer with callback in mind - * PHP doesn't pass the output to the callback defined in ob_start() since 5.4 - * - * @param callable|null $callback - * @return bool - */ - protected static function _obEndClean($callback = NULL) - { - // Pre-5.4 ob_end_clean() will pass the buffer to the callback anyways - if (version_compare(PHP_VERSION, '5.4', '<')) - return ob_end_clean(); - - $output = ob_get_contents(); - - if ($callback === NULL) - { - $hdlrs = ob_list_handlers(); - $callback = $hdlrs[ob_get_level() - 1]; - } - - return is_callable($callback) - ? ob_end_clean() AND call_user_func($callback, $output) - : ob_end_clean(); - } - } // End Kohana /** diff --git a/src/sprout/sprout_load.php b/src/sprout/sprout_load.php index 25a9dcd5c..64949cae6 100644 --- a/src/sprout/sprout_load.php +++ b/src/sprout/sprout_load.php @@ -11,18 +11,13 @@ * For more information, visit . */ -use Sprout\Helpers\CoreAdminAuth; use Sprout\Helpers\FindReplaceHtmlCode; use Sprout\Helpers\FindReplaceRichText; use Sprout\Helpers\Pdb; use Sprout\Helpers\FindReplaceText; use Sprout\Helpers\Register; -use Sprout\Helpers\SessionStats; use Sprout\Helpers\WidgetArea; - -SessionStats::init(); - Register::extraPage(1, '404 error'); Register::extraPage(2, 'Admin login message');