From 98c94a2b590dd4424d717d4c3ecfc926c164b691 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Sun, 25 Aug 2019 18:12:08 +0100
Subject: [PATCH] Start working on theme index, but screenshotting isn't
 finished yet

---
 build.sh                          | 66 ++++++++++++++++++++++++++++++-
 module_index.json                 |  4 +-
 modules/feature-theme-gallery.php | 57 ++++++++++++++++++++++++++
 peppermint.guiconfig.json         |  9 ++++-
 themes/blue/theme.css             |  1 +
 themes/default/theme.css          |  1 +
 themes/none/theme.css             |  1 +
 7 files changed, 134 insertions(+), 5 deletions(-)
 create mode 100644 modules/feature-theme-gallery.php

diff --git a/build.sh b/build.sh
index 4c7ff63..0309294 100755
--- a/build.sh
+++ b/build.sh
@@ -55,6 +55,10 @@ task_setup() {
 	check_command git true;
 	check_command npm true;
 	check_command npm true;
+	check_command jq true optional;
+	[[ "$?" -eq 0 ]] || echo -e "${FYEL}${HC}Warning: jq is required to update the theme index.${RS}";
+	check_command firefox true optional;
+	[[ "$?" -eq 0 ]] || echo -e "${FYEL}${HC}Warning: firefox is required to generate the theme previews.${RS}";
 	
 	task_end $?;
 	
@@ -84,6 +88,66 @@ task_build() {
 	task_end $?;
 }
 
+task_themes() {
+	if [[ ! -f "${server_pid_file}" ]]; then
+		tasks_run start-server; # TODO: Change this to call PHP directly, so we don't open the browser? Or maybe add an environment variable to start-server.
+	fi
+	
+	task_begin "Updating theme index";
+	cp "themes/themeindex.json" "themes/themeindex.json.old";
+	
+	# Temporary firefox profile
+	tmp_profile="$(mktemp -d /tmp/peppermint-firefox-profile-XXXXXXX)";
+	
+	while read filename; do
+		subtask_begin "Processing ${filename}";
+		
+		hash="$(sha256sum "${filename}" | cut -d' ' -f1)";
+		read -r -d "" awk_script <<'AWK'
+BEGIN {
+	items[0] = "\"hash\": \"" prop_hash "\"";
+	count=1;
+}
+/\s+\*\s+@/ {
+	atrule=$2;
+	gsub(/@/, "", atrule);
+	gsub(/\s*\*\s*@[a-z\_]+\s+/, "", $0);
+	items[count] = "\"" atrule "\": \"" $0 "\"";
+	count++;
+}
+END {
+	result="{";
+	for(i = 0; i < count; i++) {
+		result = result items[i];
+		if(i < count - 1) result = result ",";
+	}
+	print(result "}");
+}
+AWK
+		# TODO: Consider mapping it out as TSV, then using JQ to generate the object
+		awk -v prop_hash="${hash}" "${awk_script}" <"${filename}";
+		
+		
+		# Capture the screenshot
+		screenshot_loc_full="$(dirname "${filename}")/preview_full.png";
+		screenshot_loc_small="$(dirname "${filename}")/preview_small.png";
+		
+		# Set the theme
+		tmp_file="$(mktemp /tmp/peppermint-json-XXXXXXX)";
+		jq --arg theme_css "$(cat "${filename}")" '.css = $theme_css' <"build/peppermint.json" >"${tmp_file}";
+		mv "${tmp_file}" "build/peppermint.json";
+		
+		# Capture the full-res screenshot
+		firefox --new-instance --headless --profile "${tmp_profile}" --window-size 1920x1080 --screenshot "${screenshot_loc_full}" "http://[::1]:35623/index.php";
+		
+		subtask_end "$?";
+	done < <(find themes -type f -name "theme.css") | jq --tab --slurp . >themes/themeindex.json;
+	
+	
+	rm -r "${tmp_profile}";
+	rm "themes/themeindex.json.old";
+}
+
 task_docs() {
 	task_begin "Building HTTP API Docs";
 	node_modules/apidoc/bin/apidoc -o './docs/RestApi/' --config apidoc.json -f '.*\.php' -e 'index.php|ModuleApi'
@@ -96,7 +160,7 @@ task_docs() {
 		subtask_begin "Downloading PHPDoc";
 		# Create the temporary directory if it doesn't exist yet
 		[ -d "./build/_tmp" ] || mkdir -p "./build/_tmp/";
-		
+			
 		curl -sSL https://phpdoc.org/phpDocumentor.phar -o ./build/_tmp/phpdoc
 		subtask_end $?;
 	fi
diff --git a/module_index.json b/module_index.json
index 05788d0..e2eb526 100755
--- a/module_index.json
+++ b/module_index.json
@@ -234,10 +234,10 @@
     {
         "id": "page-help",
         "name": "Help page",
-        "version": "0.9.4",
+        "version": "0.10",
         "author": "Starbeamrainbowlabs",
         "description": "Adds a rather useful help page. Access through the 'help' action. This module also exposes help content added to Pepperminty Wiki's inbuilt invisible help section system.",
-        "lastupdate": 1566673475,
+        "lastupdate": 1566680153,
         "optional": false,
         "extra_data": []
     },
diff --git a/modules/feature-theme-gallery.php b/modules/feature-theme-gallery.php
new file mode 100644
index 0000000..51df12a
--- /dev/null
+++ b/modules/feature-theme-gallery.php
@@ -0,0 +1,57 @@
+<?php
+register_module([
+	"name" => "Theme Gallery",
+	"version" => "0.1",
+	"author" => "Starbeamrainbowlabs",
+	"description" => "Adds a theme gallery page and optional automatic theme updates.",
+	"id" => "feature-theme-gallery",
+	"code" => function() {
+		global $settings;
+		/**
+		 * @api {get} ?action=theme-gallery Display the theme gallery
+		 * @apiName ThemeGallery
+		 * @apiGroup Utility
+		 * @apiPermission Moderator
+		 */
+		
+		add_action("theme-gallery", function() {
+			global $settings;
+			
+			$themes_available = [];
+			$gallery_urls = explode(" ", $settings->css_theme_gallery_index_url);
+			foreach($gallery_urls as $url) {
+				if(empty($url)) continue;
+				$next_obj = json_decode(file_get_contents($url));
+				if($next_obj === null) {
+					http_response_code(503);
+					exit(page_renderer::render_main("Error - Theme Gallery - $settings->sitename", "<p>Error: Failed to download theme index file from <code>" . htmlentities($url) . "</code>."));
+				}
+				
+				$themes_available = array_merge(
+					$themes_available,
+					$next_obj
+				);
+			}
+			
+			$content = "<h1>Theme Gallery</h1>
+			<div class='grid theme-list'>\n";
+			foreach($themes_available as $theme) {
+				$content .= "<div class='theme-item'>
+					<input type='radio' id='" . htmlentities($theme["id"]) . "' name='theme-selector' value='" . htmlentities($theme["id"]) . "'  /><br />
+					<a href='" . htmlentities($theme["preview_large"]) . "'><img src='" . htmlentities($theme["preview_small"]) . "' title='Click to enlarge.' /></a>
+					<label for='" . htmlentities($theme["id"]) . "'>" . htmlentities($theme["name"]) . "</label>
+					<p>" . str_replace("\n", "</p>\n<p>", htmlentities($theme["description"])) . "</p>
+					<p>By <a href='" . htmlentities($theme["author_link"]) . "'>" . htmlentities($theme["author"]) . "</a> (<a href='" . htmlentities($theme["url"]) . "'>View CSS</a>)
+				</div>";
+			}
+			$content .= "</div>";
+			
+			exit(page_renderer::render_main("Theme Gallery - $settings->sitename", ""));
+			
+		});
+		
+		add_help_section("26-random-redirect", "Jumping to a random page", "<p>$settings->sitename has a function that can send you to a random page. To use it, click <a href='?action=random'>here</a>. $settings->admindetails_name ($settings->sitename's adminstrator) may have added it to one of the menus.</p>");
+	}
+]);
+
+?>
diff --git a/peppermint.guiconfig.json b/peppermint.guiconfig.json
index 2ed8c7d..cd6f0a4 100644
--- a/peppermint.guiconfig.json
+++ b/peppermint.guiconfig.json
@@ -177,6 +177,11 @@
 	"stats_update_processingtime": { "type": "number", "description": "The maximum number of milliseconds that should be spent at once calculating statistics. If some statistics couldn't fit within this limit, then they are scheduled and updated on the next page load. Note that this is a target only - if an individual statistic takes longer than this, then it won't be interrupted. Defaults to 100ms.", "default": 100},
 	"sessionprefix": { "type": "text", "description": "You shouldn't need to change this. The prefix that should be used in the names of the session variables. Defaults to \"auto\", which automatically generates this field. See the readme for more information.", "default": "auto" },
 	"sessionlifetime": { "type": "number", "description": "Again, you shouldn't need to change this under normal circumstances. This setting controls the lifetime of a login session. Defaults to 24 hours, but it may get cut off sooner depending on the underlying PHP session lifetime.", "default": 86400 },
-	"disable_peppermint_access_check": { "type": "checkbox", "description": "Disables the access check for peppermint.json on first-run. VERY DANGEROUS. Use only for development. Note that it's recommend to block access to peppermint.json for a reason - it contains your site secret and password hashes, so an attacker could do all <em>sorts</em> of nefarious things if it's left unblocked.", "default": false },
-	"css": { "type": "textarea", "description": "A string of css to include. Will be included in the &lt;head&gt; of every page inside a &lt;style&gt; tag. This may also be an absolute url - urls will be referenced via a &lt;link rel='stylesheet' /&gt; tag.", "default": "auto" }
+	"disable_peppermint_access_check": { "type": "checkbox", "description": "Disables the access check for peppermint.json on first-run. <strong>VERY DANGEROUS</strong>. Use only for development. Note that it's recommend to block access to peppermint.json for a reason - it contains your site secret and password hashes, so an attacker could do all <em>sorts</em> of nefarious things if it's left unblocked.", "default": false },
+	"css_theme_autoupdate_url": { "type": "url", "description": "A url that points to the css theme file to check for updates. If blank, then automatic updates are disabled.", "default": "" },
+	"css_theme_autoupdate_interval": { "type": "number", "description": "The interval, in seconds, that updates to the theme should be checked for. Defaults to every week. A value of -1 disables automatic updates.", "default": 604800 },
+	"css_theme_gallery_index_url": { "type": "text", "description": "A url that points  to an index file that contains a list of themes. Used to populate the gallary. Multiple urls are allowed - separate them with a space." },
+	"css_theme_gallery_selected_id": { "type": "text", "description": "The id of the currently selected theme. Defaults to the internal default theme.", "default": "default" },
+	"css": { "type": "textarea", "description": "A string of css to include. Will be included in the &lt;head&gt; of every page inside a &lt;style&gt; tag. This may also be an absolute url - urls will be referenced via a &lt;link rel='stylesheet' /&gt; tag. If the theme gallery is installed and automatic updates enabled, then the value of this property is managed by the theme gallery and changes may be overwritten (try the css_custom setting instead).", "default": "auto" },
+	"css_custom": { "type": "textarea", "description": "A string of custom CSS to include on top of the base theme css. Allows for theme customisations while still enabling automatic updates :D Just like the css setting, this one can also be a url.", "default": "/* Enter your custom css here. */" }
 }
diff --git a/themes/blue/theme.css b/themes/blue/theme.css
index 77bb411..2ff0732 100644
--- a/themes/blue/theme.css
+++ b/themes/blue/theme.css
@@ -4,6 +4,7 @@
  * @description	A more blue theme.
  * @author		ZestyclosePainting
  * @author_link	https://reddit.com/u/ZestyclosePainting
+ * @minversion	0.20
  * 
  * "A more blue theme for pepperminty if anyone's interested."
  * By u/ZestyclosePainting on Reddit: https://www.reddit.com/user/ZestyclosePainting/
diff --git a/themes/default/theme.css b/themes/default/theme.css
index 33d2f47..3c43c14 100644
--- a/themes/default/theme.css
+++ b/themes/default/theme.css
@@ -4,6 +4,7 @@
  * @description		The default theme.
  * @author			Starbeamrainbowlabs
  * @author_link		https://starbeamrainbowlabs.com/
+ * @minversion		0.20
  */
 
 body { margin: 2rem 0 0 0; background: #eee8f2; line-height: 1.45em; color: #111111; font-family: sans-serif; }
diff --git a/themes/none/theme.css b/themes/none/theme.css
index fc4886d..bed6ace 100644
--- a/themes/none/theme.css
+++ b/themes/none/theme.css
@@ -4,4 +4,5 @@
  * @description		A special theme with no CSS. Useful if you want to create your own complete new theme!
  * @author			Starbeamrainbowlabs
  * @author_link		https://starbeamrainbowlabs.com/
+ * @minversion		0.20
  */
