<?php
	// Falls es sich um eine Fehlerseite handelt, den HTTP-Fehlercode senden:
	if (get_cats() === 'error')
		http_response_code(get_tags());

	// Umleitung alter WP-Seiten nach dem Format /?p=123
	RedirectOldWpPage();

	$locale = $lang . '_' . $country; // locale-String zur Formatierung von internationalen Datumsangaben
	setlocale(LC_ALL, "$locale.utf8"); // locale-String für die Sortierung von Listen setzen

	date_default_timezone_set($timezone);

	// Sprachdatei inkludieren
	if ($lang !== 'en' && file_exists("lib/lang-$lang.php"))
		include_once "lib/lang-$lang.php";
	else
		include_once "lib/lang-en.php";

	// Basis-URL ermitteln (ohne / am Ende!) u.a. für Metadaten und Sitemap
	$baseUrl = GetBaseUrl();

	// Variablen für InternalProtectedLink
	$token = TokenValid(true);
	$LinkForms = [];

	// Titel und Beschreibung für den HTML-Header
	$htmlTitle = get_title() . ' – ' . $websiteName;
	$htmlDescription = get_description();

	// Getter für Config-Variablen (zur Verwendung in Widgets etc.)
	function get_lang()					{ global $lang;			return $lang; }
	function get_baseUrl()				{ global $baseUrl;		return $baseUrl; }

	// Array mit CSS-Variablen einlesen
	$cssVars = [ '--ContentBgImage' => '', '--WebsiteIcon' => '' ];
	ReadCssVariables();

	// Getter für die Header-Variablen
	// -> Wenn die Variable fehlt oder leer ist, wird ein Defaultwert zurückgegeben.
	function get_type()					{ global $type;			return def_type($type); }
	function get_published()			{ global $published;	return def_published($published); }
	function get_title()				{ global $title;		return def_title($title); }
	function get_description()			{ global $description;	return def_description($description); }
	function get_image()				{ global $image;		return def_image($image); }
	function get_cats()					{ global $cats;			return def_cats($cats); }
	function get_tags()					{ global $tags;			return def_tags($tags); }
	function get_date()					{ global $date;			return def_date($date); }
	function get_listed()				{ global $listed;		return def_listed($listed, get_type()); }
	function get_pinned()				{ global $pinned;		return def_pinned($pinned); }
	function get_front()				{ global $front;		return def_front($front); }
	function get_noindex()				{ global $noindex;		return def_noindex($noindex); }
	function get_nofollow()				{ global $nofollow;		return def_nofollow($nofollow); }
	function get_noarchive()			{ global $noarchive;	return def_noarchive($noarchive); }

	// Defaultwerte für die Header-Variablen
	// - Wenn der angegebene Wert fehlt oder leer ist, wird ein Defaultwert zurückgegeben.
	function def_type($value)			{ 						return !empty($value) ? $value : 'post'; }
	function def_published($value)		{ 						return !empty($value) ? $value : false; }
	function def_title($value)			{ 						return !empty($value) ? $value : GetString('InsertPageTitle'); }
	function def_description($value)	{ 						return !empty($value) ? $value : GetString('InsertPageDescription'); }
	function def_image($value)			{ global $cssVars;		return !empty($value) ? $value : $cssVars['--ContentBgImage']; }
	function def_cats($value)			{ 						return !empty($value) ? $value : ''; }
	function def_tags($value)			{ 						return !empty($value) ? $value : ''; }
	function def_date($value)			{ 						return !empty($value) ? $value : date('Y-m-d', getlastmod()); }
	function def_listed($value, $type)	{ 						return  isset($value) ? $value : ($type === 'post' ? true : false); }
	function def_pinned($value)			{ 						return  isset($value) ? $value : false; }
	function def_front($value)			{ 						return  isset($value) ? $value : false; }
	function def_noindex($value)		{ 						return  isset($value) ? $value : false; }
	function def_nofollow($value)		{ 						return  isset($value) ? $value : false; }
	function def_noarchive($value)		{ 						return  isset($value) ? $value : false; }

	$indexFile = ''; // Beitragsübersicht

	// Dateinamen und Header-Variablen der PHP-Dateien (Artikel) im aktuellen Verzeichnis ermitteln 
	$fileTypes = array();
	$filePublished = array();
	$fileTitles = array();
	$fileDescriptions = array();
	$fileImages = array();
	$fileCats = array();
	$catSums = array();
	$fileTags = array();
	$tagSums = array();
	$fileDates = array();
	$fileListed = array();
	$filePinned = array();
	$fileFront = array();
	$fileNoindex = array();
	$fileNofollow = array();
	$fileNoarchive = array();

	function GetFileData()
	{
		global $baseUrl;
		global $indexFile;
		global $fileTypes;
		global $filePublished;
		global $fileTitles;
		global $fileDescriptions;
		global $fileImages;
		global $fileCats;
		global $catSums;
		global $fileTags;
		global $tagSums;
		global $fileDates;
		global $fileListed;
		global $filePinned;
		global $fileFront;
		global $fileNoindex;
		global $fileNofollow;
		global $fileNoarchive;

		// Header-Variablen der PHP-Dateien (Artikel) einlesen
		$files = scandir('.');
		sort($files, SORT_LOCALE_STRING);

		// Zur Performance-Optimierung die Daten serverseitig cachen und nur neu einlesen, wenn geändert.

		// a) Dateiliste mit Dateinamen und Modifiziert-Zeitstempel erstellen:
		$FileList = '';
		$mtimes = [];
		foreach ($files as $file)
		{
			if (!IsValidFile($file)) continue;

			$mtime = filemtime($file);
			$FileList .= "$file|$mtime\n";
			$mtimes[$file] = $mtime;
			$fileDates[$file] = def_date(null); // Defaultwert für Datum setzen. Falls in der Datei ein Datum angegeben ist, wird dieser später damit überschrieben.
		}

		// b) Dateiliste vom Server laden, mit lokaler Dateiliste vergleichen und zurückschreiben:
		$FileListServer = file_exists('.filelist.txt') ? file_get_contents('.filelist.txt') : false;
		$filesChanged = !$FileListServer || ($FileListServer != $FileList);
		if ($filesChanged) file_put_contents('.filelist.txt', $FileList, LOCK_EX);

		// c) Wenn Dateien unverändert, Dateidaten vom Server laden:
		$FileDataLines = !$filesChanged && file_exists('.filedata.txt') ? file('.filedata.txt', FILE_IGNORE_NEW_LINES) : false;

		// d) Wenn Dateien verändert oder Fehler beim Laden der Dateidaten vom Server, dann Dateidaten neu ermitteln:
		if (!$FileDataLines)
		{
			$mediafiles = scandir('media/');
			sort($mediafiles, SORT_LOCALE_STRING);

			$FileDataLines = array();
			$MediaUsageLines = array();

			foreach ($files as $file)
			{
				if (is_dir($file)) continue;
				$lines = file($file, FILE_IGNORE_NEW_LINES);
				// Artikel
				if (IsValidFile($file))
				{
					$insidePhp = false;
					foreach ($lines as $line)
					{
						$line = trim($line);
						if ($insidePhp)
						{
							if ($line === '?>') break;
							if (preg_match('/^[\s]*\$[\w]+/', $line))
							{
								array_push($FileDataLines, "$file|$line");
							}
						}
						else if ($line === '<?php') $insidePhp = true;
					}
				}
				// Medienverwendung
				if (preg_match('/\.php$/', $file))
				{
					$line = implode(PHP_EOL, $lines);
					foreach ($mediafiles as $mediafile)
					{
						if (preg_match('/\b(media\/' . $mediafile . ')\b/', $line))
						{
							array_push($MediaUsageLines, "$file|$mediafile");
						}
					}
				}
			}
			// Ermittelte Dateidaten auf dem Server speichern:
			file_put_contents('.filedata.txt', implode(PHP_EOL, $FileDataLines), LOCK_EX);
			file_put_contents('.media-usage.txt', implode(PHP_EOL, $MediaUsageLines), LOCK_EX);
		}

		// Eingelesene Header-Variablen parsen
		// $FileDataLines enthält Zeilen wie z.B.:
		// template.php|$cats = 'Kategorie 1, Kategorie 2'; // kommaseparierte Liste von Beitragskategorien
		foreach ($FileDataLines as $line)
		{
			$parts = explode('|', $line);
			$file = trim($parts[0]); // template.php
			$name = ''; $value = '';
			ParseHeaderVariables($parts[1], $name, $value);

			if (empty($value))
				continue;

			if ($name === 'type')
				$fileTypes[$file] = $value;
			else if ($name === 'published')
				$filePublished[$file] = ($value === 'true');
			else if ($name === 'title')
				$fileTitles[$file] = $value;
			else if ($name === 'description')
				$fileDescriptions[$file] = $value;
			else if ($name === 'image')
				$fileImages[$file] = $value;
			else if ($name === 'cats')
			{
				$fileCats[$file] = $value;
				if ($filePublished[$file] ?? null)
				{
					$values = explode(',', $value);
					foreach ($values as $cat)
					{
						$cat = trim($cat);
						if (array_key_exists($cat, $catSums))
							$catSums[$cat]++;
						else
							$catSums[$cat] = 1;
					}
				}
			}
			else if ($name === 'tags')
			{
				$fileTags[$file] = $value;
				if ($filePublished[$file] ?? null)
				{
					$values = explode(',', $value);
					foreach ($values as $tag)
					{
						$tag = trim($tag);
						if (array_key_exists($tag, $tagSums))
							$tagSums[$tag]++;
						else
							$tagSums[$tag] = 1;
					}
				}
			}
			else if ($name === 'date')
				$fileDates[$file] = $value;
			else if ($name === 'listed')
				$fileListed[$file] = ($value === 'true');
			else if ($name === 'pinned')
				$filePinned[$file] = ($value === 'true');
			else if ($name === 'front')
				$fileFront[$file] = ($value === 'true');
			else if ($name === 'noindex')
				$fileNoindex[$file] = ($value === 'true');
			else if ($name === 'nofollow')
				$fileNofollow[$file] = ($value === 'true');
			else if ($name === 'noarchive')
				$fileNoarchive[$file] = ($value === 'true');

			// Beitragsübersicht?
			if (empty($indexFile) && ($filePublished[$file] ?? null) && ($fileTypes[$file] ?? null) === 'index')
				$indexFile = $file;
		}
		arsort($fileDates, SORT_LOCALE_STRING);
		ksort($catSums, SORT_LOCALE_STRING);
		ksort($tagSums, SORT_LOCALE_STRING);

		$indexFile = MakeLink($indexFile);

		// If files were changed, create sitemap.xml and feed.rss
		if ($filesChanged)
		{
			// sitemap.xml
			$baseUrl = preg_replace('/\/lib$/', '', $baseUrl);
			$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
			foreach ($fileDates as $file => $file_date)
			{
				$file_published = $filePublished[$file] ?? null;
				if ($file_published)
				{
					$loc = "$baseUrl/" . MakeLink($file);
					$lastmod = date('Y-m-d\TH:i:sP', $mtimes[$file]);
					$xml .= "\t<url>\n\t\t<loc>$loc</loc>\n\t\t<lastmod>$lastmod</lastmod>\n\t</url>\n";
				}
			}
			$xml .= "</urlset>";
			file_put_contents('sitemap.xml', $xml, LOCK_EX);

			// feed.rss
			global $websiteName;
			global $headerSubtitle;
			global $lang;
			global $cssVars;
			$rssLastBuildDate = (new DateTimeImmutable())->format(DateTimeInterface::RFC2822);
			$rssChannelPubDate = '';
			$rssItems = '';
			foreach ($fileDates as $file => $file_date)
			{
				$file_type = def_type($fileTypes[$file] ?? null);
				$file_published = def_published($filePublished[$file] ?? null);
				$file_title = def_title($fileTitles[$file] ?? null);
				$file_description = $fileDescriptions[$file] ?? '';
				$file_image = def_image($fileImages[$file] ?? null);

				if ($file_type === 'post' && $file_published)
				{
					$rssItemPubDate = (new DateTimeImmutable($file_date))->format(DateTimeInterface::RFC2822);
					if (empty($rssChannelPubDate))
						$rssChannelPubDate = $rssItemPubDate;
					$rssItemLink = "$baseUrl/" . MakeLink($file);
					$rssItemImageMimeType = mime_content_type($file_image);
					$rssItemImageLength = filesize($file_image);

					$rssItems .= "		<item>\n";
					$rssItems .= "			<title>$file_title</title>\n";
#					$rssItems .= "			<description>$file_description</description>\n";
					$rssItems .= "			<description><![CDATA[<p><a href=\"$rssItemLink\" target=\"_blank\"><img src=\"$baseUrl/$file_image\"></a></p><p>$file_description [<a href=\"$rssItemLink\" target=\"_blank\">mehr</a>]</p> ]]></description>\n";
					$rssItems .= "			<link>$rssItemLink</link>\n";
					$rssItems .= "			<guid>$rssItemLink</guid>\n";
					$rssItems .= "			<pubDate>" . $rssItemPubDate . "</pubDate>\n";
					$rssItems .= "			<media:content url=\"$baseUrl/$file_image\" type=\"$rssItemImageMimeType\" medium=\"image\" />\n";
					$rssItems .= "			<media:thumbnail url=\"$baseUrl/$file_image\" />\n";
					$rssItems .= "			<enclosure url=\"$baseUrl/$file_image\" length=\"$rssItemImageLength\" type=\"$rssItemImageMimeType\" />\n";
					$rssItems .= "			<content:encoded><![CDATA[<p><a href=\"$rssItemLink\"><img src=\"$baseUrl/$file_image\"></a></p><p>$file_description [<a href=\"$rssItemLink\">mehr</a>]</p> ]]></content:encoded>\n";
					$rssItems .= "		</item>\n";
				}
			}

			$xml = '<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
	<channel>
		<title>' . $websiteName . '</title>
		<link>' . $baseUrl . '</link>
		<description>' . preg_replace('/[ ]+/', ' ', str_replace('|', '', $headerSubtitle)) . '</description>
		<language>' . $lang . '</language>
		<lastBuildDate>' . $rssLastBuildDate . '</lastBuildDate>
		<pubDate>' . $rssChannelPubDate . '</pubDate>
		<image>
			<url>' . "$baseUrl/" . $cssVars['--WebsiteIcon'] . '</url>
			<title>' . $websiteName . '</title>
			<link>' . $baseUrl . '</link>
		</image>
' . $rssItems . '	</channel>
</rss>';
			file_put_contents('feed.rss', $xml, LOCK_EX);
		}
	}

	// Ermittelt, ob der angegebene Dateiname gültig ist für die Beitragsliste
	function IsValidFile($file)
	{
		if (is_dir($file)) return false; # Keine Verzeichnisse!
		if (!preg_match('/\.php$/', $file)) return false; # Nur PHP-Dateien!
		if (preg_match('/^[!#-\._~]/', $file)) return false; # Keine "versteckten" Dateien, die einem der Zeichen !#-._~ beginnen!
		return true;
	}

	function ParseHeaderVariables($line, &$name, &$value)
	{
		$assignment = trim($line); // 	$cats = 'Kategorie 1, Kategorie 2'; // kommaseparierte Liste von Beitragskategorien; optional
		$pos = strrpos($assignment, '//'); // 37
		if ($pos !== false) $assignment = substr($assignment, 0, $pos); // 	$cats = 'Kategorie 1, Kategorie 2'; 
		$pos = strrpos($assignment, ';'); // 35
		if ($pos !== false) $assignment = substr($assignment, 0, $pos); // 	$cats = 'Kategorie 1, Kategorie 2'
		$name = trim($assignment); // $cats = 'Kategorie 1, Kategorie 2'
		$value = '';
		$pos = strpos($name, '='); // 6
		if ($pos !== false)
		{
			$value = substr($name, $pos + 1); //  'Kategorie 1, Kategorie 2'
			$name = substr($name, 0, $pos); // $cats 
		}
		$name = trim(trim($name), '$'); // cats
		$value = trim(trim($value), '\''); // Kategorie 1, Kategorie 2

		// Sonderbehandung bestimmter Header-Variablen
		if ($name === 'title' || $name === 'description')
			$value = DecodeHtml($value);
	}

	function EncodeHtml($string, $eol = true)
	{
		$string = str_replace('&', '&amp;', $string);

		$string = str_replace('<', '&lt;', $string);
		$string = str_replace('>', '&gt;', $string);
		$string = str_replace('"', '&quot;', $string);

		if ($eol)
			$string = str_replace("\n", '<br>', $string);

		return $string;
	}

	function DecodeHtml($string)
	{
		$string = str_replace('<br>', "\n", $string);

		$string = str_replace('&lt;', '<', $string);
		$string = str_replace('&gt;', '>', $string);
		$string = str_replace('&quot;', '"', $string);

		$string = str_replace('&amp;', '&', $string);

		return $string;
	}

	// Bearbeitet einen HTML-String so, dass er bei | zuerst umgebrochen wird
	// und die Teile zwischen den | zuletzt umgebrochen werden.
	function NoWrap($string)
	{
		$result = $string;
		$arr = explode('|', $string);
		if (count($arr) > 0)
		{
			$result = '';
			foreach ($arr as $part)
			{
				$part = trim($part);
				if ($result !== '') $result .= ' ';
				$result .= '<span style="display: inline-block;">' . $part . '</span>';
			}
		}
		return $result;
	}

	// Kategorien und Schlagwörter unterhalb des Beitrags ausgeben
	function PrintCatsOrTags($csv, $class)
	{
		global $indexFile;

		if (empty(trim($csv))) return;
		$arr = explode(',', $csv);
		if (count($arr) > 0)
		{
			$items = array();
			foreach ($arr as $item) $items[trim($item)] = 1;
			ksort($items, SORT_LOCALE_STRING);
			echo "<p class=\"$class\">\n";
			foreach ($items as $item => $value)
			{
				echo "<a href=\"$indexFile\?$class=$item\">$item</a>";
			}
			echo "</p>\n";
		}
	}

	function FilterPassed($file, $filterCat, $filterTag, $filterMonth)
	{
		global $fileCats;
		global $fileTags;
		global $fileDates;

		if (!empty($filterCat))
		{
			if (array_key_exists($file, $fileCats))
			{
				$Cats = explode(',', $fileCats[$file]);
				foreach ($Cats as $cat) if ($filterCat === trim($cat)) return true;
			}
			return false;
		}

		if (!empty($filterTag))
		{
			if (array_key_exists($file, $fileTags))
			{
				$Tags = explode(',', $fileTags[$file]);
				foreach ($Tags as $tag) if ($filterTag === trim($tag)) return true;
			}
			return false;
		}

		if (!empty($filterMonth))
		{
			if (array_key_exists($file, $fileDates))
			{
				$file_date = $fileDates[$file];
				if (substr($file_date, 0, 7) === $filterMonth) return true;
			}
			return false;
		}

		return true;
	}

	function Search($file, $searchTerm)
	{
		if (empty($searchTerm))
			return true;

		$text = strip_tags(file_get_contents($file));
		return preg_match("/$searchTerm/i", $text);
	}

	function MakeDisplayDate($date)
	{
		global $locale;

		$displayDate = '';
		if (!empty($date))
		{
			$formatter = new IntlDateFormatter($locale, IntlDateFormatter::FULL, IntlDateFormatter::NONE);
			$displayDate = $formatter->format(new DateTime($date));
			$displayDate = "<time datetime=\"$date\">$displayDate</time>";
		}
		return $displayDate;
	}

	// Entfernt die Dateiendung ".php" vom Dateinamen. Diese soll im Browser nicht angezeigt werden.
	// In der .htaccess muss eine RewriteRule existieren, die Links ohne .php intern wieder auf .php umleitet.
	// Des weiteren wird der Dateiname "index" entfernt.
	function MakeLink($file)
	{
		$link = preg_replace('/\.php$/', '', $file);
		$link = preg_replace('/^index$/', '', $link);
		return $link;
	}

	// Ausgabe des Artikel-Headers (Überschriften)
	function DisplayEntryHeader($type, $title, $date, $description, $image, $file, $pinned, $front)
	{
		$displayDate = $type === 'post' ? MakeDisplayDate($date) : '';

		$classArticle = '';
		$styleArticle = '';

		if ($pinned)
			$classArticle .= 'pinned ';

		if ($front)
			$classArticle .= 'front ';

		if (!empty($image))
		{
			$classArticle .= 'image ';
			$styleArticle = "style=\"background-image: url('$image');\"";
		}

		echo "<article class=\"$classArticle\">\n<div class=\"article-header\" $styleArticle></div>\n<div class=\"article-content\">\n";

		if (!empty($file))
		{
			$link = MakeLink($file);
			if (empty($link)) $link = '/';
			echo "<h1><a href=\"$link\">$title</a></h1>\n";
		}
		else
		{
			echo "<h1>$title</h1>\n";
		}

		echo "<h6>$displayDate</h6>\n";

		if ($front)
		{
			$contents = file_get_contents($file);
			$contents = trim(preg_replace('/<\?php.*?\?>/s', '', trim($contents), 1)); // Ersten PHP-Codeblock entfernen.
			$contents = trim(strrev(preg_replace('/>\?.*?php\?</s', '', trim(strrev($contents)), 1))); // Letzten PHP-Codeblock entfernen; dazu alles umdrehen...
//			echo $contents; // Problem: PHP-Code wird nicht ausgeführt, darum so:
			$tmpfile = tmpfile();
			fwrite ($tmpfile, $contents);
			include (stream_get_meta_data ($tmpfile)['uri']);
			fclose($tmpfile);
		}
		else if (!empty($description))
		{
			echo "<p>$description</p>\n";
		}
	}

	// Ausgabe des Haupt-Inhalts der Seite zwischen <main> und </main>
	function DisplayMainContentBegin()
	{
		global $locale;

		global $fileTypes;
		global $filePublished;
		global $fileTitles;
		global $fileDescriptions;
		global $fileImages;
		global $fileCats;
		global $catSums;
		global $fileTags;
		global $tagSums;
		global $fileDates;
		global $fileListed;
		global $filePinned;
		global $fileFront;

		// Artikel-Übersicht (Beiträge, Seiten)
		if (get_type() === 'index')
		{
			// Filter-Hinweis oben auf der Index-Seite
			$filtered = false;

			$filterCat = htmlspecialchars($_GET['cats'] ?? '');
			if (!empty($filterCat))
			{
				$filtered = true;
				echo "<h1 class=\"filter\">" . GetString('ArticlesWithCategory') . ": $filterCat</h1>";
			}

			$filterTag = htmlspecialchars($_GET['tags'] ?? '');
			if (!empty($filterTag))
			{
				$filtered = true;
				echo "<h1 class=\"filter\">" . GetString('ArticlesWithTag') . ": $filterTag</h1>";
			}

			$filterMonth = htmlspecialchars($_GET['month'] ?? '');
			if (!empty($filterMonth))
			{
				$filtered = true;
				$displayMonth = $filterMonth;
				$formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
				$formatter->setPattern("MMMM yyyy");
				$displayMonth = $formatter->format(new DateTime("$filterMonth-01"));
				echo "<h1 class=\"filter\">" . GetString('ArticlesFrom') . " $displayMonth</h1>";
			}

			$searchTerm = htmlspecialchars($_GET['search'] ?? '');
			if (!empty($searchTerm))
			{
				$filtered = true;
				echo "<h1 class=\"filter\">" . GetString('ArticlesWithSearchTerm') . ": $searchTerm</h1>";
			}

			$maxItems = PHP_INT_MAX; // TODO: Anzahl der Artikel pro Seite begrenzen und Blätterfunktion einbauen.
			$numItems = 0;

			// Auflistung der angepinnten Artikel
			foreach ($fileDates as $file => $file_date)
			{
				$file_type = def_type($fileTypes[$file] ?? null);
				$file_published = def_published($filePublished[$file] ?? null);
				$file_title = def_title($fileTitles[$file] ?? null);
				$file_description = def_description($fileDescriptions[$file] ?? null);
				$file_image = def_image($fileImages[$file] ?? null);
				$file_listed = def_listed($fileListed[$file] ?? null, $file_type);
				$file_pinned = def_pinned($filePinned[$file] ?? null);
				$file_front = def_front($fileFront[$file] ?? null);

				if ($file_published && $file_pinned 
				&& ($file_type === 'page' || $file_type === 'post')
				&& ($file_listed || $filtered)
				&& FilterPassed($file, $filterCat, $filterTag, $filterMonth)
				&& Search($file, $searchTerm))
				{
					if ($numItems > 0) echo "</div>\n</article>\n";
					DisplayEntryHeader($file_type, $file_title, $file_date, $file_description, $file_image, $file, $file_pinned, $file_front);
					if (++$numItems >= $maxItems) break;
				}
			}

			if ($numItems >= $maxItems) return;

			// Auflistung der nicht angepinnten Artikel
			foreach ($fileDates as $file => $file_date)
			{
				$file_type = def_type($fileTypes[$file] ?? null);
				$file_published = def_published($filePublished[$file] ?? null);
				$file_title = def_title($fileTitles[$file] ?? null);
				$file_description = def_description($fileDescriptions[$file] ?? null);
				$file_image = def_image($fileImages[$file] ?? null);
				$file_listed = def_listed($fileListed[$file] ?? null, $file_type);
				$file_pinned = def_pinned($filePinned[$file] ?? null);
				$file_front = def_front($fileFront[$file] ?? null);

				if ($file_published && !$file_pinned
				&& ($file_type === 'page' || $file_type === 'post')
				&& ($file_listed || $filtered)
				&& FilterPassed($file, $filterCat, $filterTag, $filterMonth)
				&& Search($file, $searchTerm))
				{
					if ($numItems > 0) echo "</div>\n</article>\n";
					DisplayEntryHeader($file_type, $file_title, $file_date, $file_description, $file_image, $file, $file_pinned, $file_front);
					if (++$numItems >= $maxItems) break;
				}
			}
			
			if ($numItems == 0)
			{
				echo "<article>\n<div class=\"article-header\"></div>\n<div class=\"article-content\">\n";
				echo "<p>" . GetString('NoArticlesFound') . ".</p>\n";
			}
		}
		// Einzelner Artikel (Beitrag, Seite)
		else
		{
			DisplayEntryHeader(get_type(), get_title(), get_date(), '', '', '', get_pinned(), false);
		}
	}

	// Ausgabe des Artikel-Footers (Kategorien und Tags)
	function DisplayMainContentEnd()
	{
		if (get_type() === 'post' && (!empty(get_cats()) || !empty(get_tags())))
		{
			echo '<div class="article-links">';
			PrintCatsOrTags(get_cats(), 'cats');
			PrintCatsOrTags(get_tags(), 'tags');
			echo "</div>\n";
		}
		echo "<div class=\"print\">\n";
		echo "	<p class=\"article-source\">" . GetString('Origin') . ": <script>document.write(window.location);</script>\n";
		echo "	<br>" . GetString('Modified') . ": " . date(GetString('ModifiedFormat'), getlastmod()) . "</p>\n";
		echo "</div>\n";
		echo "</div>\n</article>\n";
	}

	// Ausgabe des Menüs
	function PrintMenu($xmlMenu = null)
	{
		if ($xmlMenu === null)
			$xmlMenu = file_get_contents('lib/menu.xml');
		if ($xmlMenu)
		{
			$menu = new SimpleXMLElement($xmlMenu);
			ProcessXmlMenuElement($menu);
		}
	}

	function ProcessXmlMenuElement($menu)
	{
		global $token;

		foreach ($menu->children() as $child)
		{
			$resultIcons = array();
			if ($child['icons']) preg_match_all('/./u', $child['icons'], $resultIcons);

			if ($child->getName() == 'menu')
			{
				echo '<div>';
				ProcessXmlMenuElement($child);
				echo '</div>';
			}
			else if ($child->getName() == 'item')
			{
				if ($child['link'])
				{
					$target = ($child['newtab'] == 'true' ? '_blank' : '');
					if ($child['type'] == 'protected')
						InternalProtectedLink($child['link'], (string) $child, '', $target);
					else
						echo '<a href="' . $child['link'] . '" target="' . $target . '">' . (string) $child . '</a>';
				}
				else
				{
					echo '<a>' . (string) $child . '</a>';

					if ($child['type'])
					{
						echo '<div>';
						if ($child['type'] == 'recent-posts')
						{
							$max = 6; if ($child['max']) $max = $child['max'];
							PrintMenuItemsRecentPosts($max, $resultIcons[0]);
						}
						else if ($child['type'] == 'categories')
						{
							$max = 20; if ($child['max']) $max = $child['max'];
							PrintMenuItemsCategories($max, $resultIcons[0]);
						}
						else if ($child['type'] == 'design')
						{
							PrintMenuItemsDesign($resultIcons[0]);
						}
						echo '</div>';
					}
				}
			}
		}
	}

	function PrintMenuItemsRecentPosts($maxItems, &$icons)
	{
		global $fileTypes;
		global $filePublished;
		global $fileDates;
		global $fileTitles;

		$numItems = 0;

		if (count($fileDates) > 0)
		{
			foreach ($fileDates as $file => $date)
			{
				$type = def_type($fileTypes[$file] ?? null);
				$published = def_published($filePublished[$file] ?? null);
				$title = def_title($fileTitles[$file] ?? null);

				if ($type === 'post' && $published)
				{
					echo '<a href="' . MakeLink($file) . '">' . GetMenuIcon($icons, $index) . $title . '</a>';
					if (++$numItems >= $maxItems) break;
				}
			}
		}
	}

	function PrintMenuItemsCategories($maxItems, &$icons)
	{
		global $indexFile;
		global $catSums;

		$numItems = 0;

		if (count($catSums) > 0)
		{
			foreach ($catSums as $cat => $n)
			{
				echo '<a href="./' . $indexFile . '?cats=' . $cat . '">' . GetMenuIcon($icons, $index) . $cat . ' (' . $n . ')</a>';
				if (++$numItems >= $maxItems) break;
			}
		}
	}

	function PrintMenuItemsDesign(&$icons)
	{
		echo '<a href="javascript:SwitchDesign(\'auto\');">' . GetMenuIcon($icons, $index) . GetString('DesignAuto') . '<span id="icon-design-auto"></span></a>';
		echo '<a href="javascript:SwitchDesign(\'light\');">' . GetMenuIcon($icons, $index) . GetString('DesignLight') . '<span id="icon-design-light"></span></a>';
		echo '<a href="javascript:SwitchDesign(\'dark\');">' . GetMenuIcon($icons, $index) . GetString('DesignDark') . '<span id="icon-design-dark"></span></a>';
	}

	function GetMenuIcon(&$icons, &$index)
	{
		$n = $icons ? count($icons) : 0;
		$icon = '';
		if ($n > 0)
		{
			$index %= $n;
			$icon = $icons[$index] . ' ';
			$index++;
		}
		return $icon;
	}

	// Ausgabe der Widgets
	$widgets; // Widget-Daten
	function GetWidgetsData($xmlWidgets = null)
	{
		global $widgets;
		if ($widgets)
			return true;
		if ($xmlWidgets === null)
			$xmlWidgets = file_get_contents('widgets/widgets.xml');
		if ($xmlWidgets)
		{
			$widgets = new SimpleXMLElement($xmlWidgets);
			return true;
		}
		return false;
	}

	function PrintWidgets($xmlWidgets = null)
	{
		global $widgets;
		if (GetWidgetsData($xmlWidgets))
		{
			$shuffledWidgets = array();
			foreach ($widgets->item as $item)
			{
				if ($item['position'] && $item['position'] != 'sidebar')
					continue;
				if ($item['shuffle'] == 'true')
					array_push($shuffledWidgets, $item);
			}
			shuffle($shuffledWidgets);
			foreach ($widgets->item as $item)
			{
				if ($item['position'] && $item['position'] != 'sidebar')
					continue;
				if ($item['shuffle'] == 'true')
					$item = array_shift($shuffledWidgets);
				echo '<div class="widget">';
				$filename = $item['filename'];
				$heading = (string) $item;
				if (preg_match('/\.xml$/', $filename))
				{
					$xmlWidget = file_get_contents('widgets/' . $filename);
					if ($xmlWidget)
						PrintWidgetXML($xmlWidget, $heading);
				}
				else
				{
					echo '<h1>' . $heading . '</h1>';
					include 'widgets/' . $filename;
				}
				echo '</div>';
			}
		}
	}

	function PrintWidgetXML($xmlWidget, $heading)
	{
		global $fileDates;
		global $fileTypes;
		global $filePublished;
		global $fileListed;

		global $fileTitles;
		global $fileDescriptions;
		global $fileImages;

		$widget = new SimpleXMLElement($xmlWidget);
		$style = $widget['style'];

		if ($widget['type'] == 'link')
		{
			$link = $widget['link'] ?? '';
			$image = $widget['image'] ?? '';
			$image_light = $widget['image_light'] ?? '';
			$image_dark = $widget['image_dark'] ?? '';
			$alt = $widget['alt'] ?? '';
			$heading = $widget['heading'] ?? $heading;
			$subtitle = $widget['subtitle'] ?? '';

			$n = count($widget->frame);
			if ($n > 0)
			{
				$n = mt_rand() % $n;
				$frame = $widget->frame[$n];
				if ($frame->link) $link = (string) $frame->link;
				if ($frame->image) $image = (string) $frame->image;
				if ($frame->image_light) $image_light = (string) $frame->image_light;
				if ($frame->image_dark) $image_dark = (string) $frame->image_dark;
				if ($frame->alt) $alt = (string) $frame->alt;
				if ($frame->heading) $heading = (string) $frame->heading;
				if ($frame->subtitle) $subtitle = (string) $frame->subtitle;
				if ($frame->style) $style = (string) $frame->style;
			}
			PrintWidget($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle, $style);
		}
		else if ($widget['type'] == 'random')
		{
			// a) Artikel zählen
			$count = 0;
			foreach ($fileDates as $file => $file_date)
			{
				$file_type = def_type($fileTypes[$file] ?? null);
				$file_published = def_published($filePublished[$file] ?? null);
				$file_listed = def_listed($fileListed[$file] ?? null, $file_type);

				if ($file_published
				&& ($file_type === 'page' || $file_type === 'post')
				&& $file_listed) $count++;
			}

			$index = mt_rand() % $count;

			// b) Artikel wählen
			$count = 0;
			foreach ($fileDates as $file => $file_date)
			{
				$file_type = def_type($fileTypes[$file] ?? null);
				$file_published = def_published($filePublished[$file] ?? null);
				$file_listed = def_listed($fileListed[$file] ?? null, $file_type);

				if ($file_published
				&& ($file_type === 'page' || $file_type === 'post')
				&& $file_listed)
				{
					if ($count === $index) break;
					else $count++;
				}
			}

			// b) Artikel zeigen
			$link = MakeLink($file);
			$heading = def_title($fileTitles[$file] ?? null);
			$image = def_image($fileImages[$file] ?? null);
			$subtitle = def_description($fileDescriptions[$file] ?? null);
			PrintWidget($link, $image, '', '', '', $heading, $subtitle, $style);
		}
	}

	function PrintWidget($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle, $style)
	{
		if (!$style || $style == '' || $style == 'default')
			PrintWidgetAsDefault($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle);
		else if ($style == 'card')
			PrintWidgetAsCard($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle);
	}

	function PrintWidgetAsDefault($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle)
	{
		echo '<a href="' . $link . '">';
		echo '	<div>';
		echo '		<h1>' . BBtoHTML($heading) . '</h1>';
		echo '		<p>';
		if (!empty($image)) echo '<img src="' . $image . '" alt="' . $alt . '" />';
		if (!empty($image_light)) echo '<img src="' . $image_light . '" alt="' . $alt . '" class="light" />';
		if (!empty($image_dark)) echo '<img src="' . $image_dark . '" alt="' . $alt . '" class="dark" />';
		echo '</p>';
		echo '		<p>' . BBtoHTML($subtitle) . '</p>';
		echo '	</div>';
		echo '</a>';
	}

	function PrintWidgetAsCard($link, $image, $image_light, $image_dark, $alt, $heading, $subtitle)
	{
		echo '<div class="widget-card">';
		echo '	<a href="' . $link . '">';
		if (!empty($image)) echo '<img src="' . $image . '" alt="' . $alt . '" />';
		if (!empty($image_light)) echo '<img src="' . $image_light . '" alt="' . $alt . '" class="light" />';
		if (!empty($image_dark)) echo '<img src="' . $image_dark . '" alt="' . $alt . '" class="dark" />';
		echo '		<div>';
		echo '			<h1>' . BBtoHTML($heading) . '</h1>';
		echo '			<p>' . BBtoHTML($subtitle) . '</p>';
		echo '		</div>';
		echo '	</a>';
		echo '</div>';
	}

	function BBtoHTML($string)
	{
		return str_replace(['[', ']'], ['<', '>'], $string);
	}

	// Ausgabe des Seiten-Footers
	function PrintFooter($xmlWidgets = null)
	{
		global $widgets;
		if (GetWidgetsData($xmlWidgets))
		{
			foreach ($widgets->item as $item)
			{
				if ($item['position'] == 'footer')
				{
					$filename = $item['filename'];
					if (file_exists("widgets/$filename"))
						include 'widgets/' . $filename;
				}
			}
		}
	}

	function PrintFooterInfotext()
	{
		global $timeStart;

		$timeEnd = microtime(true);
		$timeDiff = $timeEnd - $timeStart;
		$ms = sprintf("%.1f", $timeDiff * 1000);

		echo "<p class=\"infotext\" title=\"page generated in $ms ms\">" . sprintf(GetString('WebsiteCreatedWith'), "<b><i><a href=\"https://www.stefanbion.de/blog4u\" target=\"_blank\">Blog4u</a></i></b>") . "</p>";
	}

	function ReadCssVariables()
	{
		global $cssVars;

		$preview = array_key_exists('preview', $_GET) || basename($_SERVER['PHP_SELF']) == 'preview.php';
		$filenames = [ GetStylesheet($preview), GetUserStylesheet($preview) ];
		foreach ($filenames as $filename)
		{
			if (!empty($filename) && file_exists("lib/$filename"))
			{
				$css = file_get_contents("lib/$filename");
				if ($css)
				{
					foreach ($cssVars as $name => $value)
					{
						if (preg_match("/$name:([^;]+);/", $css, $matches))
						{
							$value = trim($matches[1]);
							if (preg_match("/url\('([^']+)'\)/", $value, $matches))
								$value = trim($matches[1]);
							$cssVars[$name] = $value;
						}
					}
				}
			}
		}
	}

	function ExistsStylesheet($preview = false)
	{
		$filename = GetStylesheet($preview);
		return !empty($filename) && file_exists("lib/$filename");
	}

	function ExistsUserStylesheet($preview = false)
	{
		$filename = GetUserStylesheet($preview);
		return !empty($filename) && file_exists("lib/$filename");
	}

	function GetStylesheet($preview = false)
	{
		global $stylesheet;
		global $stylesheetPreview;

		if ($preview === false && array_key_exists('preview', $_GET)) $preview = true;
		return $preview ? $stylesheetPreview : $stylesheet;
	}

	function GetUserStylesheet($preview = false)
	{
		global $userStylesheet;
		global $userStylesheetPreview;

		if ($preview === false && array_key_exists('preview', $_GET)) $preview = true;
		return $preview ? $userStylesheetPreview : $userStylesheet;
	}

	function GetString($name)
	{
		global $LangStrings;
		if (array_key_exists($name, $LangStrings))
			return str_replace('|', '&#10;', $LangStrings[$name]);
		else
			return 'STRING MISSING';
	}

	function GetUrlParameter($key)
	{
		return array_key_exists($key, $_POST) ? $_POST[$key] : (array_key_exists($key, $_GET) ? $_GET[$key] : '');
	}

	function GetGetValue($key)
	{
		return array_key_exists($key, $_GET) ? $_GET[$key] : '';
	}

	function GetPostValue($key)
	{
		return array_key_exists($key, $_POST) ? $_POST[$key] : '';
	}

	function GetServerValue($key)
	{
		return array_key_exists($key, $_SERVER) ? $_SERVER[$key] : '';
	}

	// Ermittelt die URL der Seite ohne den Query-String
	function GetPageUrl()
	{
		$url = $_SERVER['SCRIPT_URI'];
		if (empty($url))
		{
			$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
			$url = explode('?', $url)[0];
		}
		return $url;
	}

	// Basis-URL ermitteln (ohne / am Ende!)
	// u.a. für Metadaten und Sitemap und zur Verwendung in default.js
	function GetBaseUrl()
	{
		global $baseUrl;
		if (empty($baseUrl))
		{
			$baseUrl = '.';
			$uri = $_SERVER['SCRIPT_URI']; // z.B. "https://geliebt.info/phpinfo.html" oder "https://geliebt.info/phpinfo.php" oder "https://geliebt.info/"
			$url = $_SERVER['SCRIPT_URL']; // z.B. "/phpinfo.html"                     oder "/phpinfo.php"                     oder "/"
			$pos = strrpos($uri, $url);
			$baseUrl = ($pos === false) ? $uri : substr($uri, 0, $pos);
		}
		return rtrim($baseUrl, '/');
	}

	function GetRobotsTag()
	{
		$robots = array();

		if (get_noindex()) array_push($robots, 'noindex');
		if (get_nofollow()) array_push($robots, 'nofollow');
		if (get_noarchive()) array_push($robots, 'noarchive');

		if (!empty($robots))
		{
			$content = implode(',', $robots);
			echo "\t<meta name=\"robots\" content=\"$content\" />\n";
		}
	}

	function GetCanonicalTag()
	{
		$url = GetPageUrl();
		if (!empty($url))
		{
			if (get_type() === 'index')
			{
				$query = array();
				foreach (['cats', 'tags', 'month', 'search'] as $name)
				{
					$value = htmlspecialchars($_GET[$name] ?? '');
					if (!empty($value)) $query[$name] = $value;
				}
				if (!empty($query)) $url .= '?' . http_build_query($query);
			}
			echo "\t<link rel=\"canonical\" href=\"$url\" />\n";
		}
	}

	function GetPostDataAsBase64()
	{
		$post = array();

		foreach ($_POST as $name => $value)
			array_push($post, sprintf("%s=\"%s\"", htmlspecialchars($name), htmlspecialchars($value)));

		return (count($post) > 0) ? base64_encode(implode(', ', $post)) : '';
	}

	// Umleitung alter WP-Seiten nach dem Format /?p=123
	// - Wenn die Startseite mit dem URL-Parameter p aufgerufen wurde, dann die Seite mit der
	//   angegebenen Nummer am Ende des Dateinamens (hinter "-") suchen und stattdessen anzeigen:
	function RedirectOldWpPage()
	{
		if (preg_match('/^\/\?p=[0-9]+$/', $_SERVER['REQUEST_URI']))
		{
			$p = htmlspecialchars($_GET['p'] ?? '');
			if (!empty($p))
			{
				$files = scandir('.');
				foreach ($files as $file)
				{
					if (!is_dir($file) && preg_match("/-$p\.php$/", $file))
					{
						$basename = preg_replace('/\.php$/', '', $file);
						header("Location: /$basename?p=$p", true, 301);
						exit();
					}
				}
			}
		}
	}

	function ArticlePopupLink($text, $link)
	{
		global $fileTitles;
		global $fileDescriptions;
		global $fileImages;

		$file = preg_replace('/#.*$/', '', $link) . '.php';
		$title = def_title($fileTitles[$file] ?? null);
		$description = def_description($fileDescriptions[$file] ?? null);
		$image = def_image($fileImages[$file] ?? null);

		echo '<a class="popup" href="' . $link . '" title="[img]' . $image . '[/img][center][b]' . $title . '[/b][p]' . $description . '">' . (!empty($text) ? $text : $title)  . '</a>';
	}

	function TokenValid($init = false)
	{
		$a = 'AES-256-CBC';
		$h = $_SERVER['HTTP_HOST'];
		$i = substr(md5($h), 0, openssl_cipher_iv_length($a));
		if ($init) return openssl_encrypt(time(), $a, $h, 0, $i);
		if (!array_key_exists('token', $_POST)) return false;
		$t = openssl_decrypt(htmlspecialchars($_POST['token']), $a, $h, 0, $i);
		if ($t === false || !is_numeric($t)) return false;
		$s = time() - $t; return 2 <= $s && $s <= 3600;
	}

	function InternalProtectedLink($link, $text = '', $title = '', $link_target = '', $button_class = 'link')
	{
		echo GetInternalProtectedLink($link, $text, $title, $link_target, $button_class);
	}

	function GetInternalProtectedLink($link, $text = '', $title = '', $link_target = '', $button_class = 'link')
	{
		global $token;
		global $LinkForms;
		global $fileTitles;

		$inputPostVars = '';
		$parts = explode('?', $link);
		$link = $parts[0];
		$vars = $parts[1] ?? '';
		if ($vars)
		{
			$arrVars = [];
			foreach (explode('&', $vars) as $var)
			{
				$pair = explode('=', $var);
				$name = $pair[0];
				$value = $pair[1] ?? '';
				if ($name)
					array_push($arrVars, '<input id="' . $name . '" type="hidden" name="input-' . $name . '" value="' . $value . '">');
				$inputPostVars = implode($arrVars);
			}
		}

		$file = preg_replace('/#.*$/', '', $link) . '.php';
		$file_title = def_title($fileTitles[$file] ?? $title);
		$id = 'form-link-' . count($LinkForms);

		array_push($LinkForms, '<form id="' . $id . '" action="' . $link . '" method="post" target="' . $link_target . '" style="display: none;"><input type="hidden" name="token" value="' . $token . '">' . $inputPostVars . '</form>');
		return '<button class="' . $button_class . '" type="submit" form="' . $id . '" title="' . $title . '">' . (!empty($text) ? $text : $file_title) . '</button>';
	}

	function PrintLinkForms()
	{
		global $LinkForms;
		echo '<div id="foermchen">' . implode($LinkForms) . "</div>\n";
	}

	function Settings($settingName, $settingValue, $settingInverted = false)
	{
		$settingId = sprintf('settings-%d', mt_rand());

		$strSetting = GetString("Setting-$settingName-Title");
		$strSettingSet = GetString("Setting-$settingName-Set");
		$strSettingUnset = GetString("Setting-$settingName-Unset");
		$strSettingHint = sprintf(GetString("Setting-$settingName-Hint"), $settingName, $settingValue);
		$strCookieNotice = GetString("CookieNotice");

		$htmlItemSet = <<<EOM
		<input type="radio" name="$settingId" id="$settingId-set" onchange="OnChangeSetting('$settingId', '$settingValue');"><label for="$settingId-set">$strSettingSet</label>

		EOM;

		$htmlItemUnset = <<<EOM
		<input type="radio" name="$settingId" id="$settingId-unset" onchange="OnChangeSetting('$settingId', 'unset');"><label for="$settingId-unset">$strSettingUnset</label>

		EOM;

		$htmlItems = $settingInverted ? "$htmlItemUnset<br>$htmlItemSet" : "$htmlItemSet<br>$htmlItemUnset";

		echo <<<EOM
<div class="settings" id="$settingId" data-name="$settingName" data-value-set="$settingValue" style="display: grid; place-items: center;">
	<fieldset style="display: inline-block;">
		<legend>$strSetting</legend>
		$htmlItems
	</fieldset>
	<p><a class="popup" title="$strSettingHint">$strCookieNotice</a></p>
</div>


EOM;
	}

	function InitDefault()
	{
		GetFileData();
	}
?>
