Skip to content

Commit

Permalink
feat(session): delay session boot until all plugins are loaded
Browse files Browse the repository at this point in the history
In certain situations, we want plugins to have control over the session user,
e.g. by registered custom entity classes. This breaks down the plugin boot sequence
further to allow the system to perform operations, such as session boot,
after plugin files have been loaded, and the necessary plugin registrations took
place
  • Loading branch information
hypeJunction committed Jul 19, 2018
1 parent f1763a7 commit dd81b84
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 26 deletions.
6 changes: 5 additions & 1 deletion engine/classes/Elgg/Application.php
Expand Up @@ -192,10 +192,14 @@ public static function isCoreLoaded() {
*
* This method loads the full Elgg engine, checks the installation
* state, and triggers a series of events to finish booting Elgg:
* - {@elgg_event boot system}
* - {@elgg_event plugins_load system}
* - {@elgg_event plugins_boot system}
* - {@elgg_event init system}
* - {@elgg_event ready system}
*
* Please note that the Elgg session is started after all plugins are loader, there will therefore
* be no information about a logged user available until plugins_load,system event is complete.
*
* If Elgg is not fully installed, the browser will be redirected to an installation page.
*
* @return void
Expand Down
17 changes: 14 additions & 3 deletions engine/classes/Elgg/Application/BootHandler.php
Expand Up @@ -116,14 +116,25 @@ public function bootPlugins() {

$events = $this->app->_services->events;

$events->registerHandler('plugins_load:after', 'system', function() {
_elgg_session_boot($this->app->_services);
});

$events->registerHandler('plugins_boot:before', 'system', 'elgg_views_boot');
$events->registerHandler('plugins_boot', 'system', '_elgg_register_routes');
$events->registerHandler('plugins_boot', 'system', '_elgg_register_actions');

// Load the plugins that are active
$this->app->_services->plugins->load();
// Setup all boot sequence handlers for active plugins
$this->app->_services->plugins->build();

// Register plugin classes, entities etc
// Call PluginBootstrap::load()
// After event completes, Elgg session is booted
$events->triggerSequence('plugins_load', 'system');

// Allows registering handlers strictly before all init, system handlers
// Boot plugin, setup languages and views
// Include start.php
// Call PluginBootstrap::boot()
$events->triggerSequence('plugins_boot', 'system');

$config->_plugins_boot_complete = true;
Expand Down
3 changes: 0 additions & 3 deletions engine/classes/Elgg/BootService.php
Expand Up @@ -142,9 +142,6 @@ public function boot(ServiceProvider $services) {
$debug = $config->hasInitialValue('debug') ? $config->getInitialValue('debug') : $config->debug;
$services->logger->setLevel($debug);

// finish boot sequence
_elgg_session_boot($services);

if ($config->system_cache_enabled) {
$config->system_cache_loaded = false;

Expand Down
71 changes: 58 additions & 13 deletions engine/classes/Elgg/Database/Plugins.php
Expand Up @@ -449,7 +449,7 @@ public function isActive($plugin_id) {
}

/**
* Loads all active plugins in the order specified in the tool admin panel.
* Registers lifecycle hooks for all active plugins sorted by their priority
*
* @note This is called on every page load. If a plugin is active and problematic, it
* will be disabled and a visible error emitted. This does not check the deps system because
Expand All @@ -458,7 +458,7 @@ public function isActive($plugin_id) {
* @return bool
* @access private
*/
public function load() {
public function build() {

$plugins_path = $this->getPath();

Expand All @@ -471,6 +471,7 @@ public function load() {
return false;
}

$this->events->registerHandler('plugins_load', 'system', [$this, 'register']);
$this->events->registerHandler('plugins_boot:before', 'system', [$this, 'boot']);
$this->events->registerHandler('init', 'system', [$this, 'init']);
$this->events->registerHandler('ready', 'system', [$this, 'ready']);
Expand All @@ -480,6 +481,41 @@ public function load() {
return true;
}

/**
* Autoload plugin classes and files
* Register views, translations and custom entity types
*
* @elgg_event plugins_load system
* @return void
*
* @access private
* @internal
*/
public function register() {
$plugins = $this->find('active');
if (empty($plugins)) {
return;
}

if ($this->timer) {
$this->timer->begin([__METHOD__]);
}

foreach ($plugins as $plugin) {
try {
$setup = $plugin->register();
} catch (Exception $ex) {
$this->disable($plugin, $ex);
}
}

$this->registerRoot();

if ($this->timer) {
$this->timer->end([__METHOD__]);
}
}

/**
* Boot the plugins
*
Expand Down Expand Up @@ -518,23 +554,14 @@ public function boot() {
}

/**
* Boot root level custom plugin for starter-project installation
* Register root level plugin views and translations
* @return void
*/
protected function bootRoot() {
protected function registerRoot() {
if (Paths::project() === Paths::elgg()) {
return;
}

// This is root directory start.php
$root_start = Paths::project() . "start.php";
if (is_file($root_start)) {
$setup = Application::requireSetupFileOnce($root_start);
if ($setup instanceof \Closure) {
$setup();
}
}

// Elgg is installed as a composer dep, so try to treat the root directory
// as a custom plugin that is always loaded last and can't be disabled...
if (!$this->config->system_cache_loaded) {
Expand All @@ -555,6 +582,24 @@ protected function bootRoot() {
$this->translator->registerTranslations(Paths::project() . 'languages');
}
}
/**
* Boot root level custom plugin for starter-project installation
* @return void
*/
protected function bootRoot() {
if (Paths::project() === Paths::elgg()) {
return;
}

// This is root directory start.php
$root_start = Paths::project() . "start.php";
if (is_file($root_start)) {
$setup = Application::requireSetupFileOnce($root_start);
if ($setup instanceof \Closure) {
$setup();
}
}
}

/**
* Initialize plugins
Expand Down
25 changes: 19 additions & 6 deletions engine/classes/ElggPlugin.php
Expand Up @@ -620,6 +620,7 @@ public function activate() {
try {
_elgg_services()->events->trigger('cache:flush', 'system');

$this->register();
$setup = $this->boot();
if ($setup instanceof Closure) {
$setup();
Expand Down Expand Up @@ -777,6 +778,7 @@ public function getBootstrap() {
* Register plugin classes and require composer autoloader
*
* @return void
* @throws PluginException
* @access private
* @internal
*/
Expand All @@ -790,28 +792,39 @@ public function autoload() {
}

/**
* Boot the plugin by autoloading files, classes etc
* Autoload plugin classes and vendor libraries
* Register plugin-specific entity classes and execute bootstrapped load scripts
* Register languages and views
*
* @return void
* @throws PluginException
* @return \Closure|null
* @access private
* @internal
*/
public function boot() {
public function register() {
$this->autoload();

$this->activateEntities();
$this->registerLanguages();
$this->registerViews();

$this->getBootstrap()->load();
}

/**
* Boot the plugin
*
* @throws PluginException
* @return \Closure|null
* @access private
* @internal
*/
public function boot() {
$result = null;
if ($this->canReadFile('start.php')) {
$result = Application::requireSetupFileOnce("{$this->getPath()}start.php");
}

$this->registerLanguages();
$this->registerViews();

$this->getBootstrap()->boot();

return $result;
Expand Down
1 change: 1 addition & 0 deletions engine/tests/classes/Elgg/Plugins/PluginTesting.php
Expand Up @@ -72,6 +72,7 @@ public function startPlugin() {

// @todo Resolve plugin dependencies and activate required plugins

$plugin->register();
$setup = $plugin->boot();
if ($setup instanceof \Closure) {
$setup();
Expand Down
28 changes: 28 additions & 0 deletions engine/tests/phpunit/unit/Elgg/Application/BootHandlerUnitTest.php
Expand Up @@ -112,6 +112,9 @@ public function testCanBootApplication() {
public function testBootEventCalls() {

$calls = new \stdClass();
$calls->{'plugins_load:before'} = 0;
$calls->{'plugins_load'} = 0;
$calls->{'plugins_load:after'} = 0;
$calls->{'plugins_boot:before'} = 0;
$calls->{'plugins_boot'} = 0;
$calls->{'plugins_boot:after'} = 0;
Expand Down Expand Up @@ -139,6 +142,31 @@ public function testBootEventCalls() {
}
}

public function testCanSetCustomUserClassDuringBootSequence() {

$app = $this->createMockApplication();

$app->_services->events->registerHandler('plugins_load', 'system', function(Event $event) {
elgg_set_entity_class('user', 'custom_user', CustomUser::class);
});

$user = $this->createUser([
'subtype' => 'custom_user',
]);

$app->_services->session->set('guid', $user->guid);

$user->invalidateCache();

$app->bootCore();

$this->assertInstanceOf(CustomUser::class, $app->_services->session->getLoggedInUser());

$app->_services->session->removeLoggedInUser();
}

}

class CustomUser extends \ElggUser {

}

0 comments on commit dd81b84

Please sign in to comment.