summaryrefslogblamecommitdiffstats
path: root/modules-available/runmode/inc/runmode.inc.php
blob: 2d676cae5750675fe27def4bca94d3cfab3ef78e (plain) (tree)
1
2
3
4
5
6
7



             


                                    






                                                   
                                                                                        
           
                                                                                    



                                                             
                                    

                                                                            
                                    


                                                                                        
           
                                          


                                                                                                                                                       
                                                  
           

                                                                                                                                                        
         


                                                               

                                                                                  
                                      
                                     



                                                                                                                                  
                                                       





                                                                   
                 









                                                                                                                                                      



                            


                                                                                          
                                                                                   
           
                                                                                                            





                                                                                                                        
                                                            
                                                                                          
                                                                                                                  
           
                                                                                                         
         










                                                                                                       
                        


















                                                                                                                     
                 
                            


           


                                                                              
           
                                                                                        






                                                                                                                        
                                        












                                                                       

                                                                   
                                                                                                
                                                          

                                                                                      
           
                                                                                                                      




                                                           
                                                                                               




                                                                           
                                                                          



                                                                             
                                        


                                                                    




                                                                 

                            



                                                        
          
                                                        
                                                                                                                     

                                                       
                                                                                                  




                                              
                                                                                                                
                               
                                        
                                                                                         



                                                         







                                                                                          


                                                                                                               
           
                                                                   









                                                                                                     


                                
                                                                 

                                            
                                                                        







                                                                                                 



























                                                                         



                                                                                       


                                                                                                            
           


                                                                                         



                                                          
 




                                                                         
                                                 







                                                                         

                                                               
                                                                        
                                                                     
                                                               

         
                                                                               

                                        
                               
                                                   
                               
                                            
         
 
                                                                 



                                                                                                          
 
<?php

class RunMode
{
	const DATA_DETAILED = 1;
	const DATA_MACHINE_DATA = 2;
	const DATA_STRINGS = 4;

	private static $moduleConfigs = array();

	/**
	 * Get runmode config for a specific module
	 *
	 * @param string $module name of module
	 * @return ?RunModeModuleConfig config, null if module doesn't support run modes
	 */
	public static function getModuleConfig(string $module): ?RunModeModuleConfig
	{
		if (isset(self::$moduleConfigs[$module]))
			return self::$moduleConfigs[$module];
		if (Module::get($module) === false)
			return null;
		$file = 'modules/' . $module . '/hooks/runmode/config.json';
		if (!file_exists($file))
			return null;
		return (self::$moduleConfigs[$module] = new RunModeModuleConfig($file));
	}

	/**
	 * @param string|\Module $moduleId
	 * @param string|null $modeId an ID specific to the module to further specify the run mode, NULL to delete the run mode entry
	 * @param string|null $modeData optional, additional data for the run mode
	 * @param bool|null $isClient whether to count the machine as a client (in statistics etc.) NULL for looking at module's general runmode config
	 * @return bool whether it was set/deleted
	 */
	public static function setRunMode(string $machineuuid, $moduleId, ?string $modeId,
												 ?string $modeData = null, ?bool $isClient = null): bool
	{
		if (is_object($moduleId)) {
			$moduleId = $moduleId->getIdentifier();
		}
		// - Check if machine exists
		$machine = Statistics::getMachine($machineuuid, Machine::NO_DATA);
		if ($machine === null)
			return false;
		// - Delete entry if mode is null
		if ($modeId === null) {
			return Database::exec('DELETE FROM runmode WHERE machineuuid = :machineuuid', compact('machineuuid')) > 0;
		}
		// - Add/replace entry in runmode table
		// - Check if module provides runmode config at all
		$config = self::getModuleConfig($moduleId);
		if ($config === false)
			return false;
		if ($isClient === null) {
			$isClient = $config->isClient;
		}
		Database::exec('INSERT INTO runmode (machineuuid, module, modeid, modedata, isclient)'
			. ' VALUES (:uuid, :module, :modeid, :modedata, :isclient)'
			. ' ON DUPLICATE KEY'
			. ' UPDATE module = VALUES(module), modeid = VALUES(modeid), modedata = VALUES(modedata), isclient = VALUES(isclient)', array(
				'uuid' => $machineuuid,
				'module' => $moduleId,
				'modeid' => $modeId,
				'modedata' => $modeData,
				'isclient' => ($isClient ? 1 : 0),
		));
		return true;
	}

	/**
	 * Change the isClient flag for existing client.
	 * @param string $machineUuid existing machine with some runmode
	 * @param string $moduleId module that assigned the current runmode of that client
	 * @param bool $isClient should this machine be considered a normal client?
	 */
	public static function updateClientFlag(string $machineUuid, string $moduleId, bool $isClient): void
	{
		Database::exec('UPDATE runmode SET isclient = :isclient WHERE machineuuid = :uuid AND module = :module',
			['uuid' => $machineUuid, 'module' => $moduleId, 'isclient' => ($isClient ? 1 : 0)]);
	}

	/**
	 * @param int $returnData bitfield of data to return
	 * @return false|array {'machineuuid', 'isclient', 'module', 'modeid', 'modedata',
	 * 		('hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'), ('moduleName', 'modeName')}
	 */
	public static function getRunMode(string $machineuuid, int $returnData = self::DATA_MACHINE_DATA)
	{
		if ($returnData & self::DATA_MACHINE_DATA) {
			if ($returnData & self::DATA_DETAILED) {
				$sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen';
			} else {
				$sel = '';
			}
			$res = Database::queryFirst(
				"SELECT m.machineuuid, r.isclient, r.module, r.modeid, r.modedata $sel
					FROM machine m INNER JOIN runmode r USING (machineuuid)
					WHERE m.machineuuid = :machineuuid LIMIT 1",
				compact('machineuuid'));
		} else {
			$res = Database::queryFirst('SELECT r.machineuuid, r.isclient, r.module, r.modeid, r.modedata
					FROM runmode r
					WHERE r.machineuuid = :machineuuid LIMIT 1',
				compact('machineuuid'));
		}
		if ($res === false)
			return false;
		if ($returnData & self::DATA_STRINGS) {
			$module = Module::get($res['module']);
			if ($module === false) {
				$res['moduleName'] = $res['module'];
			} else {
				$res['moduleName'] = $module->getDisplayName();
			}
			$mode = self::getModeName($res['module'], $res['modeid']);
			if ($mode === false) {
				$mode = '???? unknown';
			}
			$res['modeName'] = $mode;
		}
		return $res;
	}

	/**
	 * @param string|Module $module
	 * @param bool $groupByModeId true = wrap in array where key is modeid
	 * @return array - format depending on $groupByModeId
	 */
	public static function getForModule($module, bool $groupByModeId = false): array
	{
		if (is_object($module)) {
			$module = $module->getIdentifier();
		}
		$res = Database::simpleQuery('SELECT machineuuid, modeid, modedata FROM runmode WHERE module = :module',
			compact('module'));
		$ret = array();
		foreach ($res as $row) {
			if ($groupByModeId) {
				if (!isset($ret[$row['modeid']])) {
					$ret[$row['modeid']] = array();
				}
				$ret[$row['modeid']][] = $row;
			} else {
				$ret[$row['machineuuid']] = $row;
			}
		}
		return $ret;
	}

	/**
	 * @param string|Module $module Module the mode belongs to
	 * @param string $modeId module-specific runmode identifier
	 * @param bool $detailed whether to return meta data about machine, not just machineuuid
	 * @param bool $assoc use machineuuid as array key
	 * @return array <key=machineuuid>, value={'machineuuid', 'modedata',
	 * 		<'hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'>}
	 */
	public static function getForMode($module, string $modeId, bool $detailed = false, bool $assoc = false): array
	{
		if (is_object($module)) {
			$module = $module->getIdentifier();
		}
		if ($detailed) {
			$sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen';
			$join = 'INNER JOIN machine m USING (machineuuid)';
		} else {
			$join = $sel = '';
		}
		$res = Database::simpleQuery(
			"SELECT r.machineuuid, r.modedata, r.isclient $sel
				FROM runmode r $join
				WHERE module = :module AND modeid = :modeId",
			compact('module', 'modeId'));
		$ret = array();
		foreach ($res as $row) {
			if ($detailed && empty($row['hostname'])) {
				$row['hostname'] = $row['clientip'];
			}
			if ($assoc) {
				$ret[$row['machineuuid']] = $row;
			} else {
				$ret[] = $row;
			}
		}
		return $ret;
	}

	/**
	 * Return assoc array of all configured clients.
	 *
	 * @param bool $withData also return data field?
	 * @param bool|null $isClient true = return clients only, false = return non-clients only, null = return both
	 * @return array all the entries from the table
	 */
	public static function getAllClients(bool $withData = false, bool $isClient = null): array
	{
		$xtra = '';
		if ($withData) {
			$xtra .= ', modedata';
		}
		$res = Database::simpleQuery("SELECT machineuuid, module, modeid, isclient $xtra FROM runmode");
		$ret = array();
		foreach ($res as $row) {
			if (!is_null($isClient) && ($row['isclient'] != 0) !== $isClient)
				continue;
			$ret[$row['machineuuid']] = $row;
		}
		return $ret;
	}

	/**
	 * Get display name of a module's mode. If the module doesn't have a getModeName
	 * method configured, the modeId is simply returned. Otherwise the return value of
	 * that method is passed through. getModeName by contract should return false if
	 * the module doesn't think the given modeId exists.
	 *
	 * @param string|Module $module module the runmode belongs to
	 * @param string $modeId module-specific runmode identifier
	 * @return string|false mode name if known, modeId as fallback, or false if mode is not known by module
	 */
	public static function getModeName($module, string $modeId)
	{
		if (is_object($module)) {
			$module = $module->getIdentifier();
		}
		$conf = self::getModuleConfig($module);
		if ($conf === false || $conf->getModeName === false || !Module::isAvailable($module))
			return $modeId;
		return call_user_func($conf->getModeName, $modeId);
	}

	/**
	 * Delete given runmode.
	 *
	 * @param string|Module $module Module runmode belongs to
	 * @param string $modeId run mode id
	 */
	public static function deleteMode($module, string $modeId): void
	{
		if (is_object($module)) {
			$module = $module->getIdentifier();
		}
		Database::exec('DELETE FROM runmode WHERE module = :module AND modeid = :modeId',
			compact('module', 'modeId'));
	}

}

/*                *\
|* Helper classes *|
\*                */

/**
 * Class RunModeModuleConfig represents desired config of a runmode
 */
class RunModeModuleConfig
{
	/**
	 * @var string|false
	 */
	public $systemdDefaultTarget = false;
	/**
	 * @var string[]
	 */
	public $systemdDisableTargets = [];
	/**
	 * @var string[]
	 */
	public $systemdEnableTargets = [];
	/**
	 * @var string Name of function that turns a modeId into a string
	 */
	public $getModeName = false;
	/**
	 * @var string Name of function that is called to add additional config entries
	 */
	public $configHook = false;
	/**
	 * @var bool Consider this a normal client that should e.g. be shown in client statistics by default
	 */
	public $isClient = false;
	/**
	 * @var bool Allow adding and removing machines to this mode via the generic form
	 */
	public $allowGenericEditor = true;
	/**
	 * @var string Snippet to construct URL for delete
	 */
	public $deleteUrlSnippet = false;

	/**
	 * @var string|false Permission to check when accessing/assigning
	 */
	public $permission = false;

	public function __construct(string $file)
	{
		$data = json_decode(file_get_contents($file), true);
		if (!is_array($data))
			return;
		$this->loadType($data, 'systemdDefaultTarget', 'string');
		$this->loadType($data, 'systemdDisableTargets', 'array');
		$this->loadType($data, 'systemdEnableTargets', 'array');
		$this->loadType($data, 'getModeName', 'string');
		$this->loadType($data, 'configHook', 'string');
		$this->loadType($data, 'isClient', 'boolean');
		$this->loadType($data, 'allowGenericEditor', 'boolean');
		$this->loadType($data, 'deleteUrlSnippet', 'string');
		$this->loadType($data, 'permission', 'string');
	}

	private function loadType(array $data, string $key, string $type): void
	{
		if (!isset($data[$key]))
			return;
		if (gettype($data[$key]) !== $type)
			return;
		$this->{$key} = $data[$key];
	}

	public function userHasPermission(?int $locationId): bool
	{
		return $this->permission === false || User::hasPermission($this->permission, $locationId);
	}

}