Access functions of BookStack in static class #4286

Closed
opened 2026-02-05 08:25:41 +03:00 by OVERLORD · 13 comments
Owner

Originally created by @ikabod on GitHub (Oct 31, 2023).

Attempted Debugging

  • I have read the debugging page

Searched GitHub Issues

  • I have searched GitHub for the issue.

Describe the Scenario

Hello,

I have a static class in a custom themes folder. This class is called via a xhr request by an index.html file located in the public folder of BookStack.

In this class I can include the app of BookStack like:

self::$app = require __DIR__ . '/../../../bootstrap/app.php';

Now I want to access some functions of BookStack, like the hasAppAccess() function in the helpers.php file.

How can I do this?

May be there is an easier way, but please not via the API, because this contains to much steps for the user to setup. My little program shall be installable by a Linux repository and work out of the box after installation without generating Tokens etc.

Thanks,
Peter

Exact BookStack Version

BookStack v23.08.3

Log Content

No response

Hosting Environment

PHP 8.2.11 on a local system (Manjaro) with manual installation.

Originally created by @ikabod on GitHub (Oct 31, 2023). ### Attempted Debugging - [X] I have read the debugging page ### Searched GitHub Issues - [X] I have searched GitHub for the issue. ### Describe the Scenario Hello, I have a static class in a custom themes folder. This class is called via a xhr request by an index.html file located in the public folder of BookStack. In this class I can include the app of BookStack like: `self::$app = require __DIR__ . '/../../../bootstrap/app.php';` Now I want to access some functions of BookStack, like the **hasAppAccess()** function in the **helpers.php** file. How can I do this? May be there is an easier way, but please not via the API, because this contains to much steps for the user to setup. My little program shall be installable by a Linux repository and work out of the box after installation without generating Tokens etc. Thanks, Peter ### Exact BookStack Version BookStack v23.08.3 ### Log Content _No response_ ### Hosting Environment PHP 8.2.11 on a local system (Manjaro) with manual installation.
OVERLORD added the 🐕 Support label 2026-02-05 08:25:41 +03:00
Author
Owner

@ssddanbrown commented on GitHub (Oct 31, 2023):

Hi @ikabod,
To be honest I'm not generally clear on what you're trying to achieve, or what you've already put together.

It sounds like you want to add a custom endpoint, but without it being part of the API (and requiring API key usage).

Our logical theme system supports adding custom endpoints. See the RSS feed hack for an example of this. Video here.

Now I want to access some functions of BookStack, like the hasAppAccess() function in the helpers.php file.

Pretty much all global helpers will be available to you when using the logical theme system functions.php file.
Note though, the hasAppAccess() helper has removed in v23.10, instead using user()->hasAppAccess().

Some of these methods will require the route/logic to be fired when the user has a session (and is potentially authenticated).
If your route just needs to require login, you could instead do something like this as your functions.php file:

<?php

use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use Illuminate\Support\Facades\Route;

Theme::listen(ThemeEvents::APP_BOOT, function () {
    Route::middleware(['web', 'auth'])->group(function () {
        Route::get('/my-custom-endpoint', function () {

            // Custom endpoint logic here
            return response()->json(['data' => 'stuff']);
        });
    });
});

Note that if calling from a seperate non-bookstack handled HTML page (even on the same domain) then you might have to consider that your page won't be creating/refreshing user sessions itself, so you might be relying on when the user last accessed BookStack. Things can get more complex if not on the same origin/domain since sessions are cookie based, and you can start dealing with CORS too.

@ssddanbrown commented on GitHub (Oct 31, 2023): Hi @ikabod, To be honest I'm not generally clear on what you're trying to achieve, or what you've already put together. It sounds like you want to add a custom endpoint, but without it being part of the API (and requiring API key usage). Our logical theme system supports adding custom endpoints. See the [RSS feed hack](https://www.bookstackapp.com/hacks/simple-page-rss-feed/) for an example of this. [Video here](https://www.youtube.com/watch?v=VYyyvaZTs_4). > Now I want to access some functions of BookStack, like the hasAppAccess() function in the helpers.php file. Pretty much all global helpers will be available to you when using the logical theme system `functions.php` file. Note though, the `hasAppAccess()` helper has removed in v23.10, instead using `user()->hasAppAccess()`. Some of these methods will require the route/logic to be fired when the user has a session (and is potentially authenticated). If your route just needs to require login, you could instead do something like this as your `functions.php` file: ```php <?php use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use Illuminate\Support\Facades\Route; Theme::listen(ThemeEvents::APP_BOOT, function () { Route::middleware(['web', 'auth'])->group(function () { Route::get('/my-custom-endpoint', function () { // Custom endpoint logic here return response()->json(['data' => 'stuff']); }); }); }); ``` Note that if calling from a seperate non-bookstack handled HTML page (even on the same domain) then you might have to consider that your page won't be creating/refreshing user sessions itself, so you might be relying on when the user last accessed BookStack. Things can get more complex if not on the same origin/domain since sessions are cookie based, and you can start dealing with CORS too.
Author
Owner

@ikabod commented on GitHub (Oct 31, 2023):

Thank you very much for you detailed answer, Dan and sorry, that I did not explained my problem more clearly, which I try now.

I have a file index.html in the public folder which contains a div and an iFrame. In the iFrame runs BookStack.

After the iFrame was loaded, I pull via a xhr request html data from the server and fill the div (e. g. with buttons).

The html depends on if the user is logged in and/or has administrator privileges:

  1. User calls the root, e. g. https://demo.bookstackapp.com/ which normally loads the index.html and not the index.php file (depends on the web server configuration)

  2. The index.html loads the iFrame (src = index.php = https://demo.bookstackapp.com/index.php)

  3. When the iFrame is loaded, the JavaScript in the index.html file makes a xhr request to the server

  4. The php script on the server wants to "know" if the user, who loaded the page in the iframe, is logged in and if so, if he is an admin

  5. If user is logged in: Return button X, if not return no buttons at all

  6. If logged in user is admin: Return button X and button Y

  7. Write the html which was returned by the php script into the div (innerHTML)

I'm not a php expert and do not know how all things in BookStack work together. My approach now is, to use the events in the functions.php to get the users data, save it into a folder in the custom theme and read it with my php script (see point 4).

For this I need to know which event I can use (I guess I tried all and none worked). The AUTH_LOGIN event sounds good, but does not work if the user activated the "remember me" option on his last login. APP_BOOT is too early. Which one can I use?

And the next problem is, how to access the users data in this event? I guess I have to add use BookStack\Users\Models\User; in the functions.php but how do I access the users data in the event?

It would be great, if you can give me a short code example.

Thanks again,
Peter

@ikabod commented on GitHub (Oct 31, 2023): Thank you very much for you detailed answer, Dan and sorry, that I did not explained my problem more clearly, which I try now. I have a file **index.html** in the public folder which contains a div and an iFrame. In the iFrame runs BookStack. After the iFrame was loaded, I pull via a xhr request html data from the server and fill the div (e. g. with buttons). The html depends on if the user is logged in and/or has administrator privileges: 1. User calls the root, e. g. https://demo.bookstackapp.com/ which normally loads the index.html and not the index.php file (depends on the web server configuration) 2. The index.html loads the iFrame (src = index.php = https://demo.bookstackapp.com/index.php) 3. When the iFrame is loaded, the JavaScript in the index.html file makes a xhr request to the server 4. The php script on the server wants to "know" if the user, who loaded the page in the iframe, is logged in and if so, if he is an admin 5. If user is logged in: Return button X, if not return no buttons at all 6. If logged in user is admin: Return button X and button Y 7. Write the html which was returned by the php script into the div (innerHTML) I'm not a php expert and do not know how all things in BookStack work together. My approach now is, to use the events in the functions.php to get the users data, save it into a folder in the custom theme and read it with my php script (see point 4). For this I need to know which event I can use (I guess I tried all and none worked). The AUTH_LOGIN event sounds good, but does not work if the user activated the "remember me" option on his last login. APP_BOOT is too early. Which one can I use? And the next problem is, how to access the users data in this event? I guess I have to add `use BookStack\Users\Models\User;` in the **functions.php** but how do I access the users data in the event? It would be great, if you can give me a short code example. Thanks again, Peter
Author
Owner

@ssddanbrown commented on GitHub (Oct 31, 2023):

Thanks for the extra detail @ikabod. This seems to be quite a complicated setup to approach this.

Can you explain the overall purpose of this index.html page and the buttons? It might be easier to just build this into the BookStack UI if that doesn't conflict with your purpose of this page.

Otherwise, you could instead just expose a JSON endpoint to return the current user and admin status, then call that via a AJAX (fetch) request from your page.

@ssddanbrown commented on GitHub (Oct 31, 2023): Thanks for the extra detail @ikabod. This seems to be quite a complicated setup to approach this. Can you explain the overall purpose of this `index.html` page and the buttons? It might be easier to just build this into the BookStack UI if that doesn't conflict with your purpose of this page. Otherwise, you could instead just expose a JSON endpoint to return the current user and admin status, then call that via a AJAX (fetch) request from your page.
Author
Owner

@ikabod commented on GitHub (Oct 31, 2023):

The functionality of the buttons are not related to BookStack, so it makes no sense to integrate it. I can not give more information about this, because I make this for a customer and I do not know if he would agree with it.

Nevertheless, my problem is not related to the buttons, but how to access the classes and functions provided by BookStack.

Do you mean with "expose a JSON endpoint" using the API? If so this is not an option now.

I may now have a workaround for this by using a custom view. In the base.blade.php I added the following code:

@php
{{ Utils::getUserData($app); }}
@endphp

In the static class Utils the function looks like:

public static function getUserData(object $app) : void
{
    var_dump($app); die('test');
}

When I load a page in BookStack I get this error message:

Symfony\Component\ErrorHandler\Error\FatalError
Allowed memory size of 268435456 bytes exhausted (tried to allocate 132128768 bytes)

As you can see the allowed memory size is bigger than the size Symfony tried to allocate. So I have a problem to understand how the things in BookStack work at all.

How can I use the $app object to get information about the user who was loading the page? $app->user is not working.

Thanks,
Peter

@ikabod commented on GitHub (Oct 31, 2023): The functionality of the buttons are not related to BookStack, so it makes no sense to integrate it. I can not give more information about this, because I make this for a customer and I do not know if he would agree with it. Nevertheless, my problem is not related to the buttons, but how to access the classes and functions provided by BookStack. Do you mean with "_expose a JSON endpoint_" using the [API](https://demo.bookstackapp.com/api/docs)? If so this is not an option now. I may now have a workaround for this by using a custom view. In the base.blade.php I added the following code: ``` @php {{ Utils::getUserData($app); }} @endphp ``` In the static class Utils the function looks like: ``` public static function getUserData(object $app) : void { var_dump($app); die('test'); } ``` When I load a page in BookStack I get this error message: **Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 268435456 bytes exhausted (tried to allocate 132128768 bytes)** As you can see the allowed memory size is bigger than the size Symfony tried to allocate. So I have a problem to understand how the things in BookStack work at all. How can I use the $app object to get information about the user who was loading the page? $app->user is not working. Thanks, Peter
Author
Owner

@ssddanbrown commented on GitHub (Nov 1, 2023):

Do you mean with "expose a JSON endpoint" using the API? If so this is not an option now.

Why? you're already getting custom code involved.
It doesn't have to be an endpoint secured with API auth tokens like the others, it can just be a JSON endpoint made available to session auth.

As an example, using the logical theme system setup a functions.php theme file with the following:

<?php

use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use Illuminate\Support\Facades\Route;

Theme::listen(ThemeEvents::APP_BOOT, function () {
    Route::middleware(['web', 'auth'])->group(function () {
        Route::get('/custom/auth-status', function () {
            return response()->json([
                'logged_in' => !user()->isGuest(),
                'current_user' => user(),
                'is_admin' => user()->hasSystemRole('admin'),
            ]);
        });
    });
});

Then go to <bookstack_url>/custom/auth-status in the browser afterwards.
Note: Custom logic is not officially supported and can break or have unexpected behaviour upon updates.

@ssddanbrown commented on GitHub (Nov 1, 2023): > Do you mean with "expose a JSON endpoint" using the [API](https://demo.bookstackapp.com/api/docs)? If so this is not an option now. Why? you're already getting custom code involved. It doesn't have to be an endpoint secured with API auth tokens like the others, it can just be a JSON endpoint made available to session auth. As an example, using [the logical theme system](https://github.com/BookStackApp/BookStack/blob/development/dev/docs/logical-theme-system.md) setup a `functions.php` theme file with the following: ```php <?php use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use Illuminate\Support\Facades\Route; Theme::listen(ThemeEvents::APP_BOOT, function () { Route::middleware(['web', 'auth'])->group(function () { Route::get('/custom/auth-status', function () { return response()->json([ 'logged_in' => !user()->isGuest(), 'current_user' => user(), 'is_admin' => user()->hasSystemRole('admin'), ]); }); }); }); ``` Then go to `<bookstack_url>/custom/auth-status` in the browser afterwards. Note: Custom logic is not officially supported and can break or have unexpected behaviour upon updates.
Author
Owner

@ikabod commented on GitHub (Nov 1, 2023):

Yes, this would work, but I need something like:

Theme::listen(ThemeEvents::APP_BOOT, function () {
    Route::middleware(['web', 'auth'])->group(function () {
        Route::get('*', function () {
            return response()->json([
                'logged_in' => !user()->isGuest(),
                'current_user' => user(),
                'is_admin' => user()->hasSystemRole('admin'),
            ]);
        });
    });
});

to fetch this data after any page was loaded, not only the specified url /custom-auth-status.

@ikabod commented on GitHub (Nov 1, 2023): Yes, this would work, but I need something like: ``` Theme::listen(ThemeEvents::APP_BOOT, function () { Route::middleware(['web', 'auth'])->group(function () { Route::get('*', function () { return response()->json([ 'logged_in' => !user()->isGuest(), 'current_user' => user(), 'is_admin' => user()->hasSystemRole('admin'), ]); }); }); }); ``` to fetch this data after any page was loaded, not only the specified url **/custom-auth-status**.
Author
Owner

@ssddanbrown commented on GitHub (Nov 1, 2023):

@ikabod I don't really understand why/how. I wouldn't consider the route "fetching" the data, it's serving it on-demand.
When you need the info on your index.html page you'd fetch this information via XHR like you originally said you were doing. So something like this within your index.html:

<script>
    (async function() {
        const response = await fetch('<bookstack_url_here>/custom/auth-status', {credentials: "include"});
        const data = await response.json();

        // Usage examples
        console.log(data);
        if (data.logged_in) {
            console.log(`Welcome ${data.current_user.name}`);
        }
    })();
</script>
@ssddanbrown commented on GitHub (Nov 1, 2023): @ikabod I don't really understand why/how. I wouldn't consider the route "fetching" the data, it's serving it on-demand. When you need the info on your `index.html` page you'd fetch this information via XHR like you originally said you were doing. So something like this within your index.html: ```html <script> (async function() { const response = await fetch('<bookstack_url_here>/custom/auth-status', {credentials: "include"}); const data = await response.json(); // Usage examples console.log(data); if (data.logged_in) { console.log(`Welcome ${data.current_user.name}`); } })(); </script> ```
Author
Owner

@ikabod commented on GitHub (Nov 1, 2023):

Please forget everything Dan, including the index.html file and the iframe.

I only need an example how to get information about the user who loaded any page (not the login page or another certain page).

  1. User loads a page
  2. functions.php file will be triggered
  3. Put some code in this functions.php file to get information about the user who loaded the page

Or alternative without functions.php:

  1. User loads a page
  2. Custom view "base.blade.php" will be triggered
  3. Put some code in the static class Utils to get information about the user who loaded the page

Code in base.blade.php:

@php
{{ Utils::getUserData($app); }}
@endphp

Not yet working code in class Utils:

public static function getUserData(object $app) : void
{
    $user = $app->user;
}

How can I use the $app var to get information about the user?

Thanks,
Peter

@ikabod commented on GitHub (Nov 1, 2023): Please forget everything Dan, including the index.html file and the iframe. I only need an example how to get information about the user who loaded **any** page (not the login page or another certain page). 1. User loads a page 2. functions.php file will be triggered 3. Put some code in this functions.php file to get information about the user who loaded the page Or alternative without functions.php: 1. User loads a page 2. Custom view "base.blade.php" will be triggered 3. Put some code in the static class Utils to get information about the user who loaded the page Code in base.blade.php: ``` @php {{ Utils::getUserData($app); }} @endphp ``` Not yet working code in class Utils: ``` public static function getUserData(object $app) : void { $user = $app->user; } ``` How can I use the $app var to get information about the user? Thanks, Peter
Author
Owner

@ssddanbrown commented on GitHub (Nov 1, 2023):

functions.php:

<?php

use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;

Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, function () {
    $user = user();

    // Dump user details for debugging
    dd($user->toArray());
});
@ssddanbrown commented on GitHub (Nov 1, 2023): ### functions.php: ```php <?php use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, function () { $user = user(); // Dump user details for debugging dd($user->toArray()); }); ```
Author
Owner

@ikabod commented on GitHub (Nov 2, 2023):

Thank you Dan, this works fine.

I guess I could not figure this out, because I thought I can only use the documented params for an event as an argument:

Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, function ($request) {

    dd($request);
    
});

How do I know, that I can use the user object in an event too? And what else objects can I use inside an event? Where can I find documentation about this?

Thanks again,
Peter

@ikabod commented on GitHub (Nov 2, 2023): Thank you Dan, this works fine. I guess I could not figure this out, because I thought I can only use the [documented params for an event](https://github.com/BookStackApp/BookStack/blob/release/app/Theming/ThemeEvents.php) as an argument: ``` Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, function ($request) { dd($request); }); ``` How do I know, that I can use the user object in an event too? And what else objects can I use inside an event? Where can I find documentation about this? Thanks again, Peter
Author
Owner

@ssddanbrown commented on GitHub (Nov 2, 2023):

The theme functions file runs as part of the app environment, so you have access to all the common helpers, classes and methods as app code.

There is no documentation about specifically what's available inside events, since it's everything available to the app itself, and it's all really considered non-supported (with potential to change).
It helps to have Laravel experience, and to use existing app code as a reference.
Additionally, one of the purposes of the hacks site is to provide examples of what can be done via various means.

@ssddanbrown commented on GitHub (Nov 2, 2023): The theme functions file runs as part of the app environment, so you have access to all the common helpers, classes and methods as app code. There is no documentation about specifically what's available inside events, since it's everything available to the app itself, and it's all really considered non-supported (with potential to change). It helps to have Laravel experience, and to use existing app code as a reference. Additionally, one of the purposes of the [hacks site](https://www.bookstackapp.com/hacks/) is to provide examples of what can be done via various means.
Author
Owner

@ikabod commented on GitHub (Nov 3, 2023):

Ok, thank you Dan.

@ikabod commented on GitHub (Nov 3, 2023): Ok, thank you Dan.
Author
Owner

@ssddanbrown commented on GitHub (Nov 3, 2023):

Happy to help.
Will therefore close this off.

@ssddanbrown commented on GitHub (Nov 3, 2023): Happy to help. Will therefore close this off.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/BookStack#4286