<?php
class Page_RunMode extends Page
{
/**
* Called before any page rendering happens - early hook to check parameters etc.
*/
protected function doPreprocess()
{
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=main');
}
$action = Request::post('action', false, 'string');
if ($action === 'save-mode') {
$this->handleSaveMode();
} elseif ($action === 'delete-machine') {
$this->handleDeleteMachine();
}
if (Request::isPost()) {
Util::redirect('?do=runmode');
}
}
private function handleSaveMode()
{
$machines = array_unique(array_filter(Request::post('machines', [], 'array'), 'is_string'));
$module = Request::post('module', false, 'string');
$modeId = Request::post('modeid', false, 'string');
$modConfig = RunMode::getModuleConfig($module);
if ($modConfig === null) {
Message::addError('runmode.module-hasnt-runmode', $module);
return;
}
if (!$modConfig->allowGenericEditor) {
Message::addError('runmode.cannot-edit-module', $module);
return;
}
$test = RunMode::getModeName($module, $modeId);
if ($test === false) {
Message::addError('runmode.invalid-modeid', $module, $modeId);
return;
}
// Query existing entries first (for delete - see below)
$existing = [];
if ($modConfig->permission !== false) {
$existing = RunMode::getForMode($module, $modeId, true, true);
}
// Before doing anything, check if the user has the proper permission for any location - if not, nothing to do
if (!$modConfig->userHasPermission(null)) {
Message::addError('main.no-permission');
Util::redirect('?do=runmode');
}
$active = 0;
foreach ($machines as $machine) {
if (isset($existing[$machine])) {
if (!$modConfig->userHasPermission($existing[$machine]['locationid'])) {
// Machine was already assigned to this runmode, and user has no permission for its location
unset($existing[$machine]);
continue; // So keep it as-is and skip
}
// User has permission to add this existing machine, keep going so meta data could be updated
unset($existing[$machine]);
} else {
// Not existing yet in this module/mode combo, but check if it is assigned to some other run mode
$machineLocation = false;
$oldMachineMode = RunMode::getRunMode($machine, RunMode::DATA_MACHINE_DATA | RunMode::DATA_DETAILED);
if ($oldMachineMode !== false) {
$machineLocation = $oldMachineMode['locationid'];
$oldModule = RunMode::getModuleConfig($oldMachineMode['module']);
if ($oldModule !== null) {
if ($oldMachineMode['module'] !== $module || $oldMachineMode['modeid'] !== $modeId) {
if (!$oldModule->allowGenericEditor || $oldModule->deleteUrlSnippet !== false) {
Message::addError('runmode.machine-still-assigned', $machine, $oldMachineMode['module']);
continue;
}
}
// Permissions for old runmode
if (!$oldModule->userHasPermission($oldMachineMode['locationid'])) {
// Show same error message as above - might help the user figure out they have no perm to remove it
Message::addError('runmode.machine-still-assigned', $machine, $oldMachineMode['module']);
continue;
}
}
} else {
// Not existing, no old mode - query machine to get location, so we can do a perm-check for new loc
$m = Statistics::getMachine($machine, Machine::NO_DATA);
if ($m !== null) {
$machineLocation = $m->locationid;
}
}
if ($machineLocation !== false && !$modConfig->userHasPermission($machineLocation)) {
Message::addError('runmode.machine-no-permission', $machine);
continue;
}
}
$ret = RunMode::setRunMode($machine, $module, $modeId, null, null);
if ($ret) {
$active++;
} else {
Message::addError('runmode.machine-not-found', $machine);
}
}
// Make sure inaccessible machines (no permission for location) are preserved on delete
// Add existing but inaccessible to list
foreach ($existing as $e) {
if (!$modConfig->userHasPermission($e['locationid'])) {
$machines[] = $e['machineuuid'];
}
}
if ($modConfig->deleteUrlSnippet === false) {
$deleted = Database::exec('DELETE FROM runmode
WHERE module = :module AND modeid = :modeId AND machineuuid NOT IN (:machines)',
compact('module', 'modeId', 'machines'));
} else {
$deleted = 0;
}
Message::addSuccess('runmode.enabled-removed-save', $active, $deleted);
Util::redirect('?do=runmode&module=' . $module . '&modeid=' . $modeId, true);
}
private function handleDeleteMachine()
{
$machineuuid = Request::post('machineuuid', false, 'string');
if ($machineuuid === false) {
Message::addError('main.parameter-missing', 'machineuuid');
return;
}
$mode = RunMode::getRunMode($machineuuid, RunMode::DATA_MACHINE_DATA | RunMode::DATA_DETAILED);
if ($mode === false) {
Message::addError('runmode.machine-not-found', $machineuuid);
return;
}
$modConfig = RunMode::getModuleConfig($mode['module']);
if ($modConfig === null) {
Message::addError('module-hasnt-runmode', $mode['moduleName']);
return;
}
if (!$modConfig->allowGenericEditor || $modConfig->deleteUrlSnippet !== false) {
Message::addError('runmode.cannot-edit-module', $mode['moduleName']);
return;
}
if (!$modConfig->userHasPermission($mode['locationid'])) {
Message::addError('runmode.machine-no-permission', $machineuuid);
return;
}
if (RunMode::setRunMode($machineuuid, null, null)) {
Message::addSuccess('machine-removed', $machineuuid);
} else {
Message::addWarning('machine-not-runmode', $machineuuid);
}
}
protected function doRender()
{
$moduleId = Request::get('module', false, 'string');
if ($moduleId !== false) {
$this->renderModule($moduleId);
return;
}
$this->renderClientList(false);
}
private function renderModule($moduleId)
{
$module = Module::get($moduleId);
if ($module === false) {
Message::addError('main.no-such-module', $moduleId);
Util::redirect('?do=runmode');
}
$module->activate(1, false);
$config = RunMode::getModuleConfig($moduleId);
if ($config === null) {
Message::addError('module-hasnt-runmode', $moduleId);
Util::redirect('?do=runmode');
}
// Given modeId?
$modeId = Request::get('modeid', false, 'string');
if ($modeId !== false) {
// Show edit page for specific module-mode combo
$this->renderModuleMode($module, $modeId, $config);
return;
}
// Permissions
if (!$config->userHasPermission(null) && !User::hasPermission('list-all')) {
Message::addError('main.no-permission');
Util::redirect('?do=runmode');
}
// Show list of machines with assigned mode for this module
$this->renderClientList($moduleId);
Render::setTitle(Page::getModule()->getDisplayName() . ' – ' . $module->getDisplayName());
}
private function renderClientList($onlyModule)
{
if ($onlyModule === false) {
$where = '';
$args = [];
} else {
$where = ' AND r.module = :moduleId ';
$args = ['moduleId' => $onlyModule];
}
$res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip, r.module, r.modeid, r.isclient"
. " FROM runmode r"
. " INNER JOIN machine m ON (m.machineuuid = r.machineuuid $where )"
. " ORDER BY m.hostname ASC, m.clientip ASC", $args);
$modules = array();
foreach ($res as $row) {
if (!isset($modules[$row['module']])) {
if (!Module::isAvailable($row['module']))
continue;
$config = RunMode::getModuleConfig($row['module']);
if ($config === null)
continue;
$modules[$row['module']] = array('config' => $config, 'list' => array());
}
if (empty($row['hostname'])) {
$row['hostname'] = $row['clientip'];
}
if ($modules[$row['module']]['config']->getModeName !== false) {
$row['mode_name'] = call_user_func($modules[$row['module']]['config']->getModeName, $row['modeid']);
}
$modules[$row['module']]['list'][] = $row;
}
$anythingOk = false;
foreach ($modules as $moduleId => $rows) {
$disabled = '';
if ($onlyModule === false) {
// Permissions - not required if rendering specific module, since it's been already done
// in $this->renderModule()
/** @var array{config: RunModeModuleConfig} $rows */
if (!$rows['config']->userHasPermission(null)) {
if (!User::hasPermission('list-all'))
continue;
$disabled = 'disabled';
} // </Permissions>
}
$anythingOk = true;
$module = Module::get($moduleId);
if ($module === false)
continue;
$config = RunMode::getModuleConfig($moduleId);
Render::addTemplate('module-machine-list', array(
'list' => $rows['list'],
'modulename' => $module->getDisplayName(),
'module' => $moduleId,
'canedit' => $config !== null && $config->allowGenericEditor && $config->deleteUrlSnippet === false,
'deleteUrl' => $config->deleteUrlSnippet,
'disabled' => $disabled,
));
}
if (!empty($modules) && !$anythingOk) {
User::assertPermission('list-all');
}
}
private function renderModuleMode(Module $module, string $modeId, RunModeModuleConfig $config)
{
$moduleId = $module->getIdentifier();
$modeName = RunMode::getModeName($moduleId, $modeId);
$redirect = Request::get('redirect', '', 'string');
if (empty($redirect)) {
$redirect = '?do=runmode';
}
if ($modeName === false) {
Message::addError('invalid-modeid', $moduleId, $modeId);
Util::redirect($redirect);
}
if (!RunMode::getModuleConfig($moduleId)->allowGenericEditor) {
Message::addError('runmode.cannot-edit-module', $moduleId);
Util::redirect($redirect);
}
// Permissions
if ($config->userHasPermission(null)) {
$disabled = '';
} elseif (User::hasPermission('list-all')) {
$disabled = 'disabled';
} else {
Message::addError('main.no-permission');
Util::redirect('?do=runmode');
}
$machines = RunMode::getForMode($module, $modeId, true);
if ($config->permission !== false) {
$allowed = User::getAllowedLocations($config->permission);
$machines = array_values(array_filter($machines, function ($item) use ($allowed) {
return in_array($item['locationid'], $allowed);
}));
}
Render::addTemplate('machine-selector', [
'module' => $moduleId,
'modeid' => $modeId,
'moduleName' => $module->getDisplayName(),
'modeName' => $modeName,
'machines' => json_encode($machines),
'redirect' => $redirect,
'disabled' => $disabled,
'add-only' => $config->deleteUrlSnippet !== false,
]);
}
protected function doAjax()
{
$action = Request::any('action', false, 'string');
if ($action !== 'getmachines')
return;
$query = Request::get('query', false, 'string');
if (strlen($query) < 2)
return;
User::load();
$config = RunMode::getModuleConfig(Request::any('module', '', 'string'));
$returnObject = ['machines' => []];
if ($config !== null) {
$params = ['query' => "%$query%"];
if ($config->permission === false) {
// Global
$condition = '1';
} else {
$params['locations'] = User::getAllowedLocations($config->permission);
$condition = 'locationid IN (:locations)';
if (in_array(0, $params['locations'])) {
$condition .= ' OR locationid IS NULL';
}
}
if ($config->permission === false || !empty($params['locations'])) {
$result = Database::simpleQuery("SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, m.locationid,
r.module, r.modeid
FROM machine m
LEFT JOIN runmode r USING (machineuuid)
WHERE ($condition) AND (machineuuid LIKE :query
OR macaddr LIKE :query
OR clientip LIKE :query
OR hostname LIKE :query)
LIMIT 100", $params);
$returnObject = [
'machines' => $result->fetchAll()
];
}
}
echo json_encode($returnObject);
}
}