<?php
	header("Content-Type: application/json; charset=UTF-8");

// 	ini_set('display_errors', 1);
	ini_set('display_startup_errors', 1);
//	ini_set('log_errors', 1);
	error_reporting(E_ALL);

	chdir('..');
	include_once 'lib/config.php';
	include 'lib/default.php';

	// Zuordnungs-Arrays target => Verzeichnis

	// Dateiverzeichnisse
	$Dirs = array
	(
		'post' => '.',
		'page' => '.',
		'index' => '.',
		'article' => '.',
		'template' => 'templates',
		'textblock' => 'textblocks',
		'snippet' => 'snippets',
		'widget' => 'widgets',
		'system' => 'lib',
		'backup' => 'backups',
		'media' => 'media'
	);

	// Archiv-Verzeichnisse (für gelöschte Dateien und Snapshots)
	$ArchiveDirs = array
	(
		'article' => 'trash',
		'snapshot' => 'snapshots',
		'media' => 'trash/media'
	);

	// Herkunftsverzeichnisse (Zielverzeichnisse zum Wiederherstellen)
	$OrigDirs = array
	(
		'article' => '.',
		'snapshot' => '.',
		'media' => 'media'
	);

	$request = GetUrlParameter('request');
	$target = GetUrlParameter('target');
	$filename = GetUrlParameter('filename');
	$filedata = GetUrlParameter('filedata');

	$success = true;
	$content = '';
	$HeaderVars = null;
	$Files = null;
	$Cats = null;
	$Tags = null;
	$MediaUsage = null;
	$Snippets = null;

	if ($request == 'list-articles')
	{
		$success = ListArticles();
	}
	else if ($request == 'list-files')
	{
		$success = ListFiles($target, $filedata);
	}
	else if ($request == 'list-dirs')
	{
		$success = ListDirs();
	}
	else if ($request == 'exists')
	{
		$success = FileExists($target, $filename, $filedata);
	}
	else if ($request == 'delete-file')
	{
		$success = DeleteFile($target, $filename);
	}
	else if ($request == 'delete-media')
	{
		$success = DeleteMediaFiles($target, $filedata);
	}
	else if ($request == 'delete-trash')
	{
		$success = DeleteTrashFiles($target, $filedata);
	}
	else if ($request == 'restore')
	{
		$success = RestoreTrashFile($target, $filename);
	}
	else if ($request == 'open')
	{
		$success = GetFileContent($target, $filename, $filedata);
	}
	else if ($request == 'save')
	{
		$success = SaveFile($target, $filename, $filedata);
	}
	else if ($request == 'upload')
	{
		$success = UploadMediaFile();
	}
	else if ($request == 'getvar')
	{
		$success = GetVariableValue($target, $filename, $filedata);
	}
	else if ($request == 'encrypt')
	{
		$success = EncryptPassword($filename, $filedata);
	}
	else if ($request == 'widget-title')
	{
		$success = GetWidgetTitle($target, $filename, $filedata);
	}
	else if ($request == 'shortcuts')
	{
		$success = GetKeyboardShortcuts($filename);
	}
	else if ($request == 'snippets')
	{
		$success = GetSnippets();
	}
	else if ($request == 'backup')
	{
		$success = Backup($target, $filename, $filedata);
	}
	else if ($request == '')
	{
		$success = false;
		$content = GetString('MissingRequest');
	}
	else
	{
		$success = false;
		$content = GetString('UnknownRequest');
	}

	// Sicherstellen, dass Dateiinhalt in UTF-8 codiert ist, sonst scheitert json_encode;
	if(!mb_check_encoding($content, 'UTF-8'))
	{
		// Windows-Zeichensatz in UTF-8 codieren.
		if(mb_check_encoding($content, 'Windows-1252'))
		{
			$content = mb_convert_encoding($content, "UTF-8", "Windows-1252");
		}
		else
		{
			$content = sprintf(GetString('InvalidCharacterEncodingFile#'), $filename);
			$success = false;
		}
	}

	// Response erstellen
	$response =
	[
		'success' => $success,
		'content' => $content
	];

	// Optionale Response-Elemente nur senden, wenn gefüllt:
	if ($HeaderVars !== null)
		$response['headerVars'] = $HeaderVars;
	if ($Files !== null)
		$response['files'] = $Files;
	if ($Cats !== null)
		$response['cats'] = $Cats;
	if ($Tags !== null)
		$response['tags'] = $Tags;
	if ($MediaUsage !== null)
		$response['mediaUsage'] = $MediaUsage;
	if ($Snippets !== null)
		$response['snippets'] = $Snippets;

	echo json_encode($response);

	/* Verwaltungsfunktionen */

	function GetFileDir($target, $dirType = null)
	{
		global $content;

		global $Dirs;
		global $ArchiveDirs;
		global $OrigDirs;

		if (empty($target))
		{
			$content = GetString('MissingTargetId');
			return false;
		}

		$refDirs = &$Dirs;

		if (!empty($dirType))
		{
			if ($dirType === 'trash') $refDirs = &$ArchiveDirs;
			else if ($dirType === 'original') $refDirs = &$OrigDirs;
			else
			{
				$content = GetString('UnknownDirectoryType');
				return false;
			}
		}

		if (!array_key_exists($target, $refDirs))
		{
			$content = GetString('UnknownTargetId');
			return false;
		}

		return $refDirs[$target];
	}

	function CorrectFilename($target, $filename, $dirType = null)
	{
		global $content;

		if (empty($target))
		{
			$content = GetString('MissingTargetId');
			return false;
		}

		$filename = basename($filename);

		if (empty($filename))
		{
			$content = GetString('MissingFilename');
			return false;
		}

		if (empty($dirType))
			if ($target === 'page' || $target === 'post'  || $target === 'index' || $target === 'article')
				$filename = pathinfo($filename, PATHINFO_FILENAME) . '.php';

		return $filename;
	}

	function DecodeFileData($filedata)
	{
		global $content;

		if (empty($filedata))
		{
			$content = GetString('MissingFileData');
			return false;
		}

		$files = json_decode($filedata, true);
		if ($files === null)
		{
			$content = GetString('ErrorReadingFileData');
			return false;
		}

		return $files;
	}

	/* Service-Funktionen */

	function ListArticles()
	{
		global $Files; $Files = [];
		global $Cats; $Cats = [];
		global $Tags; $Tags = [];
		global $MediaUsage; $MediaUsage = [];

		global $fileTypes;
		global $fileTitles;
		global $fileDescriptions;
		global $fileImages;
		global $fileDates;

		global $catSums;
		global $tagSums;

		InitDefault();

		// a) Dateiliste erzeugen
		foreach ($fileDates as $file => $file_date)
		{
			$file_name = preg_replace('/\.php$/', '', $file);
			$file_type = $fileTypes[$file] ?? '';
			$file_title = $fileTitles[$file] ?? '';
			$file_description = $fileDescriptions[$file] ?? '';
			$file_image = $fileImages[$file] ?? '';

			if ($file_type === 'page' || $file_type === 'post' || $file_type === 'index')
				array_push($Files, [ "filename" => $file_name, "title" => $file_title, "description" => $file_description, "image" => $file_image, "date" => $file_date, "type" => $file_type ]);
		}

		// b) Liste der Kategorien und Schlagwörter erzeugen
		foreach ($catSums as $cat => $n) array_push($Cats, $cat);
		foreach ($tagSums as $tag => $n) array_push($Tags, $tag);

		// c) Liste der Medienverwendung erzeugen
		$MediaUsageLines = file('.media-usage.txt', FILE_IGNORE_NEW_LINES);
		foreach ($MediaUsageLines as $line)
		{
			$parts = explode('|', $line);
			$file = $parts[0];
			$mediafile = $parts[1];
			if (!array_key_exists($mediafile, $MediaUsage))
				$MediaUsage[$mediafile] = array();
			array_push($MediaUsage[$mediafile], pathinfo($file, PATHINFO_FILENAME));
		}

		return true;
	}

	function ListFiles($target, $dirType)
	{
		global $Files; $Files = [];

		$dir = GetFileDir($target, $dirType);
		if ($dir === false) return false;

		$files = scandir("$dir/");
		foreach ($files as $filename)
		{
			if (is_dir("$dir/$filename")) continue;
			if ($target !== 'system' && preg_match('/^[!#-\._~]/', $filename)) continue;

			$size; $sizeDisplay; $date; $dateDisplay; $ext; $type;
			DetermineFileInfo("$dir/$filename", $size, $sizeDisplay, $date, $dateDisplay, $ext, $type);

			array_push($Files,
			[
				"filename" => "$filename",
				"path" => "$dir/$filename",
				"size" => $size,
				"sizeDisplay" => $sizeDisplay,
				"date" => $date,
				"dateDisplay" => $dateDisplay,
				"ext" => $ext,
				"type" => $type
			]);
		}

		if ($dirType === 'trash' || $target === 'media' || $target === 'backup')
			usort($Files, 'CompareFileTimeDesc');
		else
			usort($Files, 'CompareFilePathAsc');

		if ($dirType === 'trash' && $target === 'media')
		{
			$dirHtAccess = GetFileDir('article', 'trash');
			$dirHtpasswd = dirname(__FILE__);
			file_put_contents("$dirHtAccess/.htaccess", "AuthType Basic\nAuthName \"Authentication Required\"\nAuthUserFile $dirHtpasswd/.htpasswd\nRequire valid-user\n", LOCK_EX);
		}

		return true;
	}

	function ListDirs()
	{
		global $Files; $Files = [];

		$dir = GetFileDir('article');
		if ($dir === false) return false;

		$files = scandir("$dir/");
		foreach ($files as $filename)
		{
			if (!is_dir("$dir/$filename")) continue;
			if (preg_match('/^[!#-\._~]/', $filename) && $filename !== '.') continue; # Keine "versteckten" Verzeichnisse, die einem der Zeichen !#-._~ beginnen, außer das aktuelle Verzeichnis "."!

			array_push($Files, $filename);
		}

		sort($Files, SORT_LOCALE_STRING);

		return true;
	}

	function DetermineFileInfo($path, &$size, &$sizeDisplay, &$date, &$dateDisplay, &$ext, &$type)
	{
		global $DateTimeFormat;

		$size = filesize($path);
		$locale = localeconv(); $dp = $locale['decimal_point']; $ts = $locale['thousands_sep'];
		$sizeDisplay = $size < 1024 ? number_format($size, 0, $dp, $ts) . ' B' : number_format($size / 1024, 0, $dp, $ts) . ' KB';

		$date = filemtime($path);
		$dateDisplay = date($DateTimeFormat, $date);

		$ext = pathinfo($path, PATHINFO_EXTENSION);
		$type = 'etc';
		if (preg_match('/gif|jpg|jpeg|png|svg/', $ext)) $type = 'img';
		if (preg_match('/m4a|mp3|ogg|wav/', $ext)) $type = 'audio';
		if (preg_match('/mov|mp4/', $ext)) $type = 'video';
	}

	function CompareFileTimeDesc($a, $b)
	{
		return $b['date'] <=> $a['date'];
	}

	function CompareFilePathAsc($a, $b)
	{
		return $a['path'] <=> $a['path'];
	}

	function FileExists($target, $filename, $filedata)
	{
		global $content;

		$dir = GetFileDir($target, $filedata);
		if ($dir === false) return false;

		$filename = CorrectFilename($target, $filename);
		if ($filename === false) return false;

		$content = file_exists("$dir/$filename");
		return true;
	}

	function DeleteFile($target, $filename)
	{
		global $content;

		$dir = GetFileDir($target);
		if ($dir === false) return false;

		$filename = CorrectFilename($target, $filename);
		if ($filename === false) return false;

		if ($target === 'article')
		{
			$trashDir = GetFileDir($target, 'trash');
			if ($trashDir === false) return false;
			if (!is_dir($trashDir)) mkdir($trashDir, 0750, true);

			if (!rename("$dir/$filename", "$trashDir/$filename"))
			{
				$content = sprintf(GetString('ErrorMovingFile#ToTrash'), $filename);
				return false;
			}

			ListArticles();
		}
		else if ($target === 'template' || $target === 'textblock' || $target === 'snippet' || $target === 'widget' || $target === 'system' || $target === 'backup')
		{
			if (!unlink("$dir/$filename"))
			{
				$content = sprintf(GetString('ErrorDeletingFile#'), $filename);
				return false;
			}
		}
		else
		{
			$content = GetString('UnknownTargetId');
			return false;
		}

		return true;
	}

	function DeleteMediaFiles($target, $filedata)
	{
		global $content;

		$files = DecodeFileData($filedata);
		if ($files === false) return false;

		$dir = GetFileDir($target);
		if ($dir === false) return false;

		$trashDir = GetFileDir($target, 'trash');
		if ($trashDir === false) return false;
		if (!is_dir($trashDir)) mkdir($trashDir, 0750, true);

		$numFiles = 0;
		$numFailed = 0;

		foreach ($files as $filename)
		{
			$numFiles++;
			$filename = CorrectFilename($target, $filename);
			if (!rename("$dir/$filename", "$trashDir/$filename")) $numFailed++;
		}

		if ($numFailed > 0)
			$content = sprintf(GetString('ErrorMoving#Of#Files'), $numFailed, $numFiles);

		return true;
	}

	function DeleteTrashFiles($target, $filedata)
	{
		global $content;

		$files = DecodeFileData($filedata);
		if ($files === false) return false;

		$trashDir = GetFileDir($target, 'trash');
		if ($trashDir === false) return false;

		$numFiles = 0;
		$numFailed = 0;

		foreach ($files as $filename)
		{
			$numFiles++;
			$filename = CorrectFilename($target, $filename);
			if (!unlink("$trashDir/$filename")) $numFailed++;
		}

		if ($numFailed > 0)
			$content = sprintf(GetString('ErrorDeleting#Of#Files'), $numFailed, $numFiles);

		return true;
	}

	function RestoreTrashFile($target, $filename)
	{
		global $content;

		$trashDir = GetFileDir($target, 'trash');
		if ($trashDir === false) return false;

		$origDir = GetFileDir($target, 'original');
		if ($origDir === false) return false;

		$filename = CorrectFilename($target, $filename);
		if ($filename === false) return false;

		if (!rename("$trashDir/$filename", "$origDir/$filename"))
		{
			$content = sprintf(GetString('ErrorRecoveringFile#'), $filename);
			return false;
		}

		if ($target === 'article' || $target === 'snapshot')
			ListArticles();

		return true;
	}

	function GetFileContent($target, $filename, $dirType = null)
	{
		global $HeaderVars; $HeaderVars = [];
		global $content;

		$dir = GetFileDir($target, $dirType);
		if ($dir === false) return false;

		$filename = CorrectFilename($target, $filename, $dirType);
		if ($filename === false) return false;

		if (!file_exists("$dir/$filename"))
		{
			$content = sprintf(GetString('File#NotFound'), $filename);
			return false;
		}

		$content = file_get_contents("$dir/$filename");

		if ($content === false)
		{
			$content = sprintf(GetString('ErrorReadingFile#'), $filename);
			return false;
		}

		if ($target === 'article')
		{
			// Entfernen des ersten und letzten PHP-Codeblocks:
		/*
			$content = trim(preg_replace_callback('/^(<\?php.*?\?>)|(<\?php.*?\?>)$/s', 'ParseFileHeader', trim($content)));
			// -> Leider funktioniert das Entfernen des letzten PHP-Codeblocks so nicht: Wenn dazwischen auch noch PHP-Codeblöcke sind,
			// wird alles zwischen dem zweiten und dem letzten Block entfernt. Deshalb wird hier ein kleiner Trick angewandt:
			// Der zu durchsuchende String und das Suchmuster werden umgedreht; dann steht der letzte Codeblock am Anfang und wird leicht gefunden.
		*/
			$content = trim(preg_replace_callback('/(<\?php.*?\?>)/s', 'ParseFileHeader', trim($content), 1)); // Ersten PHP-Codeblock entfernen.
			$content = trim(strrev(preg_replace('/>\?.*?php\?</s', '', trim(strrev($content)), 1))); // Letzten PHP-Codeblock entfernen; dazu alles umdrehen...
		}
		else
		{
			$HeaderVars['type'] = $target;
		}

		return true;
	}

	// Callback-Funktion für preg_replace_callback. Dieser wird ein Array mit den Suchergebnissen übergeben - in diesem Fall der erste gefundene PHP-Codeblock.
	function ParseFileHeader($matches)
	{
		global $HeaderVars;

		if (count($matches) > 1)
		{
			//echo $matches[0];
			//echo $matches[1];

			$lines = explode(PHP_EOL, $matches[1]);
			foreach ($lines as $line)
			{
				if (preg_match('/^[\s]*\$[\w]+/', $line))
				{
					$name = ''; $value = '';
					ParseHeaderVariables($line, $name, $value);
					$HeaderVars[$name] = $value;
				}
			}
		}
		return '';
	}

	function SaveFile($target, $filename, $filedata)
	{
		global $content;

		$dir = GetFileDir($target);
		if ($dir === false) return false;

		$filename = CorrectFilename($target, $filename);
		if ($filename === false) return false;

		$arrFileData = DecodeFileData($filedata);
		if ($arrFileData === false) return false;

		$isArticle = ($target === 'post' || $target === 'page' || $target === 'index');
		$html = '';

		if ($isArticle)
		{
			// Snapshot erstellen
			$fromFilename = $filename;
			$originalFilename = GetArrayValue($arrFileData, 'originalFilename', '');
			if ($originalFilename !== '')
			{
				$originalFilename = CorrectFilename($target, $originalFilename);
				$rename = GetArrayValue($arrFileData, 'rename', false);
				if ($originalFilename !== $filename && $rename)
					$fromFilename = $originalFilename;
			}

			if (file_exists("$dir/$fromFilename"))
			{
				$snapshotDir = GetFileDir('snapshot', 'trash');
				if ($snapshotDir === false) return false;
				if (!is_dir($snapshotDir)) mkdir($snapshotDir, 0750, true);

				$toFilename = AddTimestampToFilename($fromFilename);
				if (!rename("$dir/$fromFilename", "$snapshotDir/$toFilename"))
				{
					$content = sprintf(GetString('ErrorCopyingFile#IntoSnapshotDir'), $filename);
					return false;
				}
			}

			// Datei erstellen
			$type = GetArrayValue($arrFileData, 'type', 'post');
			$published = GetArrayValue($arrFileData, 'published', false) ? 'true' : 'false';
			$title = EncodeHtml(GetArrayValue($arrFileData, 'title', ''));
			$description = EncodeHtml(GetArrayValue($arrFileData, 'description', ''));
			$image = GetArrayValue($arrFileData, 'image', '');
			$cats = GetArrayValue($arrFileData, 'cats', '');
			$tags = GetArrayValue($arrFileData, 'tags', '');
			$date = GetArrayValue($arrFileData, 'date', '');
			$listed = GetArrayValue($arrFileData, 'listed', false) ? 'true' : 'false';
			$pinned = GetArrayValue($arrFileData, 'pinned', false) ? 'true' : 'false';
			$front = GetArrayValue($arrFileData, 'front', false) ? 'true' : 'false';
			$noindex = GetArrayValue($arrFileData, 'noindex', false) ? 'true' : 'false';
			$nofollow = GetArrayValue($arrFileData, 'nofollow', false) ? 'true' : 'false';
			$noarchive = GetArrayValue($arrFileData, 'noarchive', false) ? 'true' : 'false';
			$base = GetArrayValue($arrFileData, 'base', '');

			$html .= "<?php\n";
			$html .= "\t\$type = '$type';\n";
			$html .= "\t\$published = $published;\n";
			$html .= "\t\$date = '$date';\n";
			$html .= "\t\$title = '$title';\n";
			$html .= "\t\$description = '$description';\n";
			$html .= "\t\$image = '$image';\n";
			if ($target !== 'index')
			{
				$html .= "\t\$cats = '$cats';\n";
				$html .= "\t\$tags = '$tags';\n";
				$html .= "\t\$listed = $listed;\n";
				$html .= "\t\$pinned = $pinned;\n";
				$html .= "\t\$front = $front;\n";
				$html .= "\t\$noindex = $noindex;\n";
				$html .= "\t\$nofollow = $nofollow;\n";
				$html .= "\t\$noarchive = $noarchive;\n";
				if ($base !== '') $html .= "\t\$base = '$base';\n";
			}
			$html .= "\tinclude 'lib/default-header.php';\n";
			$html .= "?>\n\n";

			$html .= $arrFileData['sourcecode'];

			$html .= "\n\n<?php include 'lib/default-footer.php'; ?>\n";
		}
		else
		{
			$html .= $arrFileData['sourcecode'];
		}

		// Datei schreiben
		$result = file_put_contents("$dir/$filename", $html, LOCK_EX);

		if ($result === false)
		{
			$content = sprintf(GetString('ErrorWritingFile#'), $filename);
			return false;
		}

		if ($result !== strlen($html))
		{
			$content = sprintf(GetString('ErrorSavingFile#Completely'), $filename);
			return false;
		}

		if ($isArticle)
			ListArticles();
		return true;
	}

	function GetArrayValue($arr, $key, $valueDefault)
	{
		if (array_key_exists($key, $arr))
			return $arr[$key];
		else
			return $valueDefault;
	}

	function AddTimestampToFilename($filename)
	{
		global $locale;

		$formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
		$formatter->setPattern("_yyyyMMdd_HHmmss.");
		$timestamp = $formatter->format(time());

		$name = pathinfo($filename, PATHINFO_FILENAME);
		$ext = pathinfo($filename, PATHINFO_EXTENSION) ?? '';

		$filename = "$name$timestamp$ext";
		return $filename;
	}

	function UploadMediaFile()
	{
		global $content;

		$file = $_FILES['file'];

		if (!isset($file))
		{
			$content = GetString('MissingFilename');
			return false;
		}

		$filename = basename($file['name']);
		$tmp_filename = $file['tmp_name'];
		$dir = GetFileDir('media');

		if (!move_uploaded_file($tmp_filename, "$dir/$filename"))
		{
			$content = GetString('ErrorMovingUploadedFileToMediaFolder');
			return false;
		}

		$lastModified = GetUrlParameter('lastModified');

		if (!empty($lastModified))
		{
			touch("$dir/$filename", $lastModified);
		}

		$content = $filename;

		return true;
	}

	function GetVariableValue($target, $filename, $filedata) // $filedata nur "durchreichen"
	{
		global $content;

		$value = '';

		if ($target === 'server')
			$value = GetServerValue($filename);

		$content = [ "value" => $value, "filedata" => $filedata ];
		return true;
	}

	function EncryptPassword($plainpasswd, $filedata) // $filedata nur "durchreichen"
	{
		global $content;

		$encrypted = password_hash($plainpasswd, PASSWORD_DEFAULT);

		$content = [ "encrypted" => $encrypted, "filedata" => $filedata ];
		return true;
	}

	function GetWidgetTitle($target, $filename, $filedata) // $filedata nur "durchreichen"
	{
		global $content;

		$previewTitle = 'Widget';

		$dir = GetFileDir($target);
		if ($dir === false) return false;

		$xmlWidgets = file_get_contents("$dir/widgets.xml");
		if ($xmlWidgets)
		{
			$widgets = new SimpleXMLElement($xmlWidgets);
			foreach ($widgets->item as $item)
			{
				if ($item['filename'] == $filename) // === funktioniert nicht, weil anderer Typ
				{
					$previewTitle = (string) $item;
					break;
				}
			}
		}

		$content = [ "previewTitle" => $previewTitle, "filedata" => $filedata ];
		return true;
	}

	function GetSnippets()
	{
		global $content;
		global $Snippets; $Snippets = [];

		$dir = GetFileDir('snippet');
		if ($dir === false) return false;

		$xmlSnippets = file_get_contents("$dir/snippets.xml");
		if ($xmlSnippets)
		{
			$titles = GetKeyboardShortcutTitles('InsertSnippet');
			$snippets = new SimpleXMLElement($xmlSnippets);
			foreach ($snippets->section as $section)
			{
				$content .= '<option value="" class="separator">' . $section['caption'] . '</option>';
				foreach ($section->item as $item)
				{
					$filename = (string) $item['filename'];
					$desc = (string) $item['desc'];
					if (!empty($desc))
					{
						$desc = str_replace('{}', '&#10;', $desc);
					}
					$title = array_key_exists($filename, $titles) ? $titles[$filename] : '';
					$title = $desc . (!empty($desc) && !empty($title) ? '&#10;—————&#10;' : '') . $title;
					$content .= '<option value="' . $filename . '" title="' . $title . '">' . (string) $item . '</option>';
					$snippetContents = file_get_contents("$dir/$filename");
					if ($snippetContents === false)
						$Snippets[$filename] = sprintf(GetString('ErrorFile#NotFound'), $filename);
					else
						$Snippets[$filename] = $snippetContents;
				}
			}
		}

		return true;
	}

	function GetKeyboardShortcutTitles($commandName)
	{
		$titles = [];

		$dir = GetFileDir('system');
		if ($dir !== false)
		{
			$xmlKeyboard = file_get_contents("$dir/keyboard.xml");
			if ($xmlKeyboard)
			{
				$keyboard = new SimpleXMLElement($xmlKeyboard);
				foreach ($keyboard->shortcut as $shortcut)
				{
					foreach ($shortcut->command as $command)
					{
						$filename = $command['value'];
						if (empty($commandName) || $commandName == $command['name'])
						{
							if ($shortcut['title'])
							{
								$title = $shortcut['title'];
							}
							else
							{
								$keys = [];
								if ($shortcut['ctrl'] == 'true') array_push($keys, GetString('KeyCtrl'));
								if ($shortcut['shift'] == 'true') array_push($keys, GetString('KeyShift'));
								if ($shortcut['alt'] == 'true') array_push($keys, GetString('KeyAlt'));
								if ($shortcut['meta'] == 'true') array_push($keys, GetString('KeyMeta'));
								if ($shortcut['key'])
								{
									$key = (string) $shortcut['key'];
									$key = preg_replace('/^(Key|Digit)/', '', $key);
									array_push($keys, $key);
								}
								$title = '(' . implode('+', $keys) . ')';
							}
							$titles[(string) $filename] = $title;
						}
					}
				}
			}
		}
		return $titles;
	}

	function GetKeyboardShortcuts($filename)
	{
		global $content;

		$content = GetKeyboardShortcutTitles($filename);

		return true;
	}

	function Backup($target, $filename, $filedata)
	{
		global $content;

		$files = DecodeFileData($filedata);
		if ($files === false) return false;

		$dir = GetFileDir($target);
		if ($dir === false) return false;
		if (!is_dir($dir)) mkdir($dir, 0750, true);

		$filename = CorrectFilename($target, $filename);
		if ($filename === false) return false;

		$zip = new ZipArchive();
		if ($zip->open("$dir/$filename", ZipArchive::CREATE) !== TRUE)
		{
			$content = sprintf(GetString('UnableToCreateZipFile#'), $filename);
			return false;
		}

		foreach ($files as $dirname)
		{
			if ($dirname === '.')
				AddDirToZip($zip, $dirname, false);
			else
				AddDirToZip($zip, $dirname, true);
		}

		$zip->close();
		return true;
	}

	function AddDirToZip($zip, $dirname, $recursive)
	{
		if (is_dir($dirname))
		{
			$files = scandir("$dirname/");
			foreach ($files as $filename)
			{
				if (is_dir("$dirname/$filename"))
				{
					if ($recursive)
						if ($filename !== '.' && $filename !== '..')
							AddDirToZip($zip, "$dirname/$filename", true);
				}
				else
				{
					if ($dirname === '.')
						$zip->addFile($filename);
					else
						$zip->addFile("$dirname/$filename");
				}
			}
		}
	}

?>
