BookStack\Providers\TranslationServiceProvider::class,
// BookStack custom service providers
+ BookStack\Providers\ThemeServiceProvider::class,
BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class,
BookStack\Providers\BroadcastServiceProvider::class,
'Views' => BookStack\Facades\Views::class,
'Images' => BookStack\Facades\Images::class,
'Permissions' => BookStack\Facades\Permissions::class,
+ 'Theme' => BookStack\Facades\Theme::class,
],
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
use DOMDocument;
use DOMNodeList;
use DOMXPath;
$environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension());
$environment->addExtension(new CustomStrikeThroughExtension());
+ $environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment);
return $converter->convertToHtml($markdown);
}
--- /dev/null
+<?php namespace BookStack\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class Theme extends Facade
+{
+ /**
+ * Get the registered name of the component.
+ *
+ * @return string
+ */
+ protected static function getFacadeAccessor()
+ {
+ return 'theme';
+ }
+}
use BookStack\Actions\ActivityService;
use BookStack\Actions\ViewService;
use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Theming\ThemeService;
use BookStack\Uploads\ImageService;
use Illuminate\Support\ServiceProvider;
$this->app->singleton('permissions', function () {
return $this->app->make(PermissionService::class);
});
+
+ $this->app->singleton('theme', function () {
+ return $this->app->make(ThemeService::class);
+ });
}
}
--- /dev/null
+<?php
+
+namespace BookStack\Providers;
+
+use BookStack\Theming\ThemeEvents;
+use BookStack\Theming\ThemeService;
+use Illuminate\Support\ServiceProvider;
+
+class ThemeServiceProvider extends ServiceProvider
+{
+ /**
+ * Register services.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ $this->app->singleton(ThemeService::class, function ($app) {
+ return new ThemeService;
+ });
+ }
+
+ /**
+ * Bootstrap services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $themeService = $this->app->make(ThemeService::class);
+ $themeService->readThemeActions();
+ $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
+ }
+}
--- /dev/null
+<?php namespace BookStack\Theming;
+
+/**
+ * The ThemeEvents used within BookStack.
+ *
+ * This file details the events that BookStack may fire via the custom
+ * theme system, including event names, parameters and expected return types.
+ *
+ * This system is regarded as semi-stable.
+ * We'll look to fix issues with it or migrate old event types but
+ * events and their signatures may change in new versions of BookStack.
+ * We'd advise testing any usage of these events upon upgrade.
+ */
+class ThemeEvents
+{
+ /**
+ * Application boot-up.
+ * After main services are registered.
+ * @param \BookStack\Application $app
+ */
+ const APP_BOOT = 'app_boot';
+
+ /**
+ * Commonmark environment configure.
+ * Provides the commonmark library environment for customization
+ * before its used to render markdown content.
+ * If the listener returns a non-null value, that will be used as an environment instead.
+ * @param \League\CommonMark\ConfigurableEnvironmentInterface $environment
+ * @returns \League\CommonMark\ConfigurableEnvironmentInterface|null
+ */
+ const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
+}
\ No newline at end of file
--- /dev/null
+<?php namespace BookStack\Theming;
+
+class ThemeService
+{
+ protected $listeners = [];
+
+ /**
+ * Listen to a given custom theme event,
+ * setting up the action to be ran when the event occurs.
+ */
+ public function listen(string $event, callable $action)
+ {
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $action;
+ }
+
+ /**
+ * Dispatch the given event name.
+ * Runs any registered listeners for that event name,
+ * passing all additional variables to the listener action.
+ *
+ * If a callback returns a non-null value, this method will
+ * stop and return that value itself.
+ * @return mixed
+ */
+ public function dispatch(string $event, ...$args)
+ {
+ foreach ($this->listeners[$event] ?? [] as $action) {
+ $result = call_user_func_array($action, $args);
+ if (!is_null($result)) {
+ return $result;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Read any actions from the set theme path if the 'functions.php' file exists.
+ */
+ public function readThemeActions()
+ {
+ $themeActionsFile = theme_path('functions.php');
+ if (file_exists($themeActionsFile)) {
+ require $themeActionsFile;
+ }
+ }
+}
\ No newline at end of file