<?php
class IPxeMenu
{
/**
* @var int ID of this menu, from DB
*/
protected $menuid;
/**
* @var int 0 = disabled, otherwise, launch default option after timeout
*/
public $timeoutMs;
/**
* @var string title to display above menu
*/
public $title;
/**
* @var int menu entry id from DB
*/
public $defaultEntryId;
/**
* @var MenuEntry[]
*/
public $items = [];
public static function get(int $menuId, bool $emptyFallback = false): ?IPxeMenu
{
$menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid FROM serversetup_menu
WHERE menuid = :menuid LIMIT 1", ['menuid' => $menuId]);
if ($menu !== false)
return new IPxeMenu($menu);
if (!$emptyFallback)
return null;
return new EmptyIPxeMenu();
}
/**
* IPxeMenu constructor.
*
* @param array $menu array for according menu row
*/
public function __construct(array $menu)
{
$this->menuid = (int)$menu['menuid'];
$this->timeoutMs = (int)$menu['timeoutms'];
$this->title = (string)$menu['title'];
$defaultEntryId = $menu['defaultentryid'];
$res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.refmenuid, e.hotkey, e.title,
e.hidden, e.sortval, e.md5pass, b.module, b.data AS bootentry, b.title AS betitle
FROM serversetup_menuentry e
LEFT JOIN serversetup_bootentry b USING (entryid)
WHERE e.menuid = :menuid
ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]);
foreach ($res as $row) {
$this->items[] = new MenuEntry($row);
}
// Make sure we have a default entry if the menu isn't empty
if ($defaultEntryId === null && !empty($this->items)) {
$defaultEntryId = $this->items[0]->menuEntryId();
}
$this->defaultEntryId = (int)$defaultEntryId;
}
public function title(): string
{
return $this->title;
}
public function timeoutMs(): int
{
return $this->timeoutMs;
}
/**
* @return int Number of items in this menu
*/
public function itemCount(): int
{
return count($this->items);
}
/**
* @return MenuEntry|null Return preselected menu entry
*/
public function defaultEntry(): ?MenuEntry
{
foreach ($this->items as $item) {
if ($item->menuEntryId() === $this->defaultEntryId)
return $item;
}
return null;
}
private function maybeOverrideDefault(string $uuid)
{
$e = $this->defaultEntry();
// Shortcut - is already bwlp and timeout is reasonable (1-15s), do nothing
$defIsMl = $e !== null && substr($e->internalId(), 0, 3) === 'ml-';
$timeoutOk = $this->timeoutMs > 0 && $this->timeoutMs <= 15000;
if ($timeoutOk && $defIsMl)
return;
// No runmode module anyways
if (!Module::isAvailable('runmode'))
return;
$rm = RunMode::getRunMode($uuid);
// No runmode for this client, cannot be PVSmgr
if ($rm === false)
return;
// Is not pvsmgr
if ($rm['module'] !== 'roomplanner')
return;
// See if it's a dedicated station, if so make sure it boots into bwLehrpool
$data = json_decode($rm['modedata'], true);
if ($data['dedicatedmgr'] ?? false) {
if (!$defIsMl) {
$this->overrideDefaultToMinilinux();
}
if (!$timeoutOk) {
$this->timeoutMs = 5000;
}
}
}
/**
* Patch the menu to make sure bwLehrpool/"MiniLinux" is the default
* boot option, and set timeout to something reasonable. This is used
* for dedicated PVS managers, as they might not have a keyboard
* connected.
*/
private function overrideDefaultToMinilinux()
{
foreach ($this->items as $item) {
if (substr($item->internalId(), 0, 3) === 'ml-') {
$this->defaultEntryId = $item->menuEntryId();
return;
}
}
}
/*
*
*/
public static function forLocation(int $locationId): IPxeMenu
{
$chain = null;
if (Module::isAvailable('locations')) {
$chain = Location::getLocationRootChain($locationId);
}
if (!empty($chain)) {
$res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title,
IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid
FROM serversetup_menu m
INNER JOIN serversetup_menu_location ml USING (menuid)
WHERE ml.locationid IN (:chain)", ['chain' => $chain]);
if ($res->rowCount() > 0) {
// Make the location id key, preserving order (closest location is first)
$chain = array_flip($chain);
foreach ($res as $row) {
// Overwrite the value (numeric ascending values, useless) with menu array of according location
$chain[(int)$row['locationid']] = $row;
}
// Use first one that was found
foreach ($chain as $menu) {
if (is_array($menu)) {
return new IPxeMenu($menu);
}
}
// Should never end up here, but we'd just fall through and use the default
}
}
// We're here, no specific menu, use default
$menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid
FROM serversetup_menu
ORDER BY isdefault DESC LIMIT 1");
if ($menu === false) {
return new EmptyIPxeMenu;
}
return new IPxeMenu($menu);
}
public static function forClient(string $ip, ?string $uuid): IPxeMenu
{
$locationId = 0;
if (Module::isAvailable('locations')) {
$locationId = Location::getFromIpAndUuid($ip, $uuid);
}
$menu = self::forLocation($locationId);
if ($uuid !== null) {
// Super specialcase hackery: If this is a dedicated PVS, force the default to
// be bwlp/"minilinux"
$menu->maybeOverrideDefault($uuid);
}
return $menu;
}
}
class EmptyIPxeMenu extends IPxeMenu
{
public function __construct()
{
parent::__construct([
'menuid' => -1,
'timeoutms' => 120,
'defaultentryid' => null,
'title' => 'No menu defined',
]);
$this->items[] = new MenuEntry([
'title' => 'Please create a menu in Server-Setup first'
]);
$this->items[] = new MenuEntry([
'title' => 'Bitte erstellen Sie zunächst ein Menü'
]);
}
}