1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2026-10-27 18:52:58 +03:00
Files
bookstack/app/View/ViewBlockManager.php
Dan Brown 01b29515c8 Layouts: Updated view block ids/labels to be static
So that their metadata can be read without having to create instances
and fullfill dependancy injection just to list.

Also added title to interface prefs page.
2026-08-16 10:41:43 +01:00

196 lines
6.5 KiB
PHP

<?php
namespace BookStack\View;
use Illuminate\Contracts\Container\BindingResolutionException;
class ViewBlockManager
{
public function __construct(
protected ViewBlockPreferences $preferences,
) {
}
/**
* @var array<string, array<string, class-string<ViewBlockInterface>[]>>
*/
protected array $blocksByLocationAndPosition = [];
/**
* @var array<string, array<string, ViewBlockInterface[]>
*/
protected array $locationBlockCache = [];
/**
* Register a block type to be displayed at the given location and position.
* @param class-string<ViewBlockInterface> $blockClass
*/
public function register(string $location, string $position, string $blockClass): void
{
if (!isset($this->blocksByLocationAndPosition[$location])) {
$this->blocksByLocationAndPosition[$location] = [];
}
if (!isset($this->blocksByLocationAndPosition[$location][$position])) {
$this->blocksByLocationAndPosition[$location][$position] = [];
}
$this->blocksByLocationAndPosition[$location][$position][] = $blockClass;
}
/**
* Get all blocks registered for a given location and position, considering the
* preferences for the current user.
* @return ViewBlockInterface[]
* @throws BindingResolutionException
*/
public function getInstancesForLocationAndPositionForCurrentUser(string $location, string $position): array
{
$key = user()->id . ':' . $location;
if (isset($this->locationBlockCache[$key])) {
$blocks = $this->locationBlockCache[$key][$position] ?? [];
return $this->blocksToInstances($blocks);
}
$forLocation = $this->getForLocationForCurrentUser($location);
$this->locationBlockCache[$key] = $forLocation;
$blocks = $forLocation[$position] ?? [];
return $this->blocksToInstances($blocks);
}
/**
* Create instances of the given block classes.
* @param class-string<ViewBlockInterface>[] $blocks
* @return ViewBlockInterface[]
* @throws BindingResolutionException
*/
protected function blocksToInstances(array $blocks): array
{
return array_map(fn (string $blockClass) => app()->make($blockClass), $blocks);
}
/**
* Get all blocks registered for a given location, as sets of arrays
* keyed by position.
* @return array<string, class-string<ViewBlockInterface>[]>
*/
protected function getForLocation(string $location): array
{
$defaults = ViewBlockDefaults::getForLocation($location) ?? [];
$registered = $this->blocksByLocationAndPosition[$location] ?? [];
return array_merge_recursive($defaults, $registered);
}
/**
* Get all blocks registered for a given location, as sets of arrays
* keyed by position, for the current user.
* Same as above but with user-specific preferences applied.
* @return array<string, class-string<ViewBlockInterface>[]>
* @throws BindingResolutionException
*/
public function getForLocationForCurrentUser(string $location): array
{
$forLocation = $this->getForLocation($location);
$userBlocksByPosition = $this->preferences->getIdByPositionMap($location);
if (empty($userBlocksByPosition)) {
return $forLocation;
}
$results = [];
$blocksById = $this->blocksByPositionToIdMap($forLocation);
$idPositionMap = $this->blocksByPositionToIdPositionMap($forLocation);
$locations = array_keys($forLocation);
$locations[] = 'unused';
// Add based on user preferences
foreach ($locations as $position) {
$userBlockIds = $userBlocksByPosition[$position] ?? [];
$results[$position] = [];
foreach ($userBlockIds as $blockId) {
$block = $blocksById[$blockId] ?? null;
if ($block && isset($blocksById[$blockId])) {
$results[$position][] = $block;
unset($blocksById[$blockId]);
}
}
}
// Add remaining blocks based on their default locations
foreach ($blocksById as $block) {
$position = $idPositionMap[$block->getId()] ?? 'unused';
$results[$position][] = $block;
}
return $results;
}
/**
* Get the names of all locations where blocks are registered.
* Returns an array where the keys are location strings, and the
* values are translated labels for that location.
* @return array<string, string>
*/
public function getNamedLocations(): array
{
$labels = ViewBlockDefaults::getLocationLabels();
$defaults = ViewBlockDefaults::getLocations();
$registered = array_keys($this->blocksByLocationAndPosition);
$merged = array_unique(array_merge($defaults, $registered));
$results = [];
foreach ($merged as $location) {
$results[$location] = $labels[$location] ?? $location;
}
return $results;
}
/**
* Update user preferences for a given location to match the given layout data map.
* @param array<string, string[]> $layoutData
* @throws BindingResolutionException
*/
public function updatePreferencesFromIdPositionMap(string $location, array $layoutData): void
{
$this->preferences->storeByIdPositionMap(
$location,
$layoutData,
$this->getForLocation($location),
);
}
/**
* Convert a blocksByPosition array into a map of block IDs to blocks.
* @param array<string, class-string<ViewBlockInterface>[]> $blocksByPosition
* @return array<string, ViewBlockInterface>
*/
protected function blocksByPositionToIdMap(array $blocksByPosition): array
{
$map = [];
foreach ($blocksByPosition as $position => $blocks) {
foreach ($blocks as $block) {
$map[$block::getId()] = $block;
}
}
return $map;
}
/**
* Convert a blocksByPosition array into a map of block IDs to their positions.
* @param array<string, class-string<ViewBlockInterface>[]> $blocksByPosition
* @return array<string, string>
*/
protected function blocksByPositionToIdPositionMap(array $blocksByPosition): array
{
$map = [];
foreach ($blocksByPosition as $position => $blocks) {
foreach ($blocks as $block) {
$map[$block::getId()] = $position;
}
}
return $map;
}
}