From b38f03c0e7218762d90fa5092696b127f24f36db Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 25 Jan 2026 07:07:26 +0000
Subject: [PATCH] =Some logical flaws in Queue.php, Queue.js, ContentExecutor.php, UploadExecutor.php - particularly with timeline ordering, frontend queue updates, etc

---
 assets/js/concise/DataStore.js |  133 +++++++++++++++++++++++++++-----------------
 1 files changed, 81 insertions(+), 52 deletions(-)

diff --git a/assets/js/concise/DataStore.js b/assets/js/concise/DataStore.js
index 58ec874..29dc9c7 100644
--- a/assets/js/concise/DataStore.js
+++ b/assets/js/concise/DataStore.js
@@ -45,7 +45,7 @@
 	 * @param {object|array} configs An object defining the store, or an array of objects defining the stores
 	 * @param {number} version the database version
 	 */
-	register(name, configs = [], version = 1.2) {
+	register(name, configs = [], version = 1.25) {
 		if (!Array.isArray(configs)) configs = [configs];
 		if (configs.length === 0) return;
 
@@ -108,6 +108,7 @@
 
 			store.ignoreFilters = new Set([
 				... ['search', 'page', 'per_page', 'orderby', 'order'],
+				... ['context', 'source'],
 				... store.config.ignore
 			]);
 
@@ -517,9 +518,10 @@
 			const cached = store.cache.get(cacheKey);
 
 			if (cached && this.isCacheValid(cached, store.config.TTL)) {
+				let items = cached.items.map(itemId => this.get(name, itemId));
 				this.notify(name, 'data-loaded', {
 					cached: true,
-					items: cached.items || []
+					items: items??[]
 				});
 				return cached;
 			}
@@ -1074,42 +1076,57 @@
 
 		// First check if we have cached results for exact filters
 		if (cacheEntry?.items) {
-			return this.applyOrdering(
-				cacheEntry.items.reduce((acc, id) => {
-					const item = store.data.get(id);
-					if (item) acc.push(item);
-					return acc;
-				}, []),
-				store
-			);
+			const items = cacheEntry.items.reduce((acc, id) => {
+				const item = store.data.get(id);
+				if (item) acc.push(item);
+				return acc;
+			}, []);
+			return this.applyOrdering(items, store);
 		}
 
 		const allItems = Array.from(store.data.values());
+
 		const searchQuery = store.filters.search?.toLowerCase().trim() || '';
 
 		const filterPredicates = [];
+
+		// Handle taxonomy filters separately
+		if (store.filters.taxonomy && typeof store.filters.taxonomy === 'object') {
+			Object.entries(store.filters.taxonomy).forEach(([taxonomy, termIds]) => {
+				const acceptedTermIds = Array.isArray(termIds) ? termIds : [termIds];
+
+				filterPredicates.push(item => {
+					if (!item.taxonomies || !item.taxonomies[taxonomy]) {
+						return false;
+					}
+					const itemTermIds = Object.keys(item.taxonomies[taxonomy]).map(id => parseInt(id));
+					const matches = acceptedTermIds.some(termId => itemTermIds.includes(parseInt(termId)));
+					return matches;
+				});
+			});
+		}
+
+		// Handle other filters
 		for (const [key, value] of Object.entries(store.filters)) {
-			if (store.ignoreFilters.has(key)) continue;
+			if (key === 'taxonomy') continue;
+			if (store.ignoreFilters.has(key)) {
+				continue;
+			}
 			if (value === null || value === undefined || value === '') continue;
 			if (value === 'all') continue;
 
-			// Comma-separated values
 			if (typeof value === 'string' && value.includes(',')) {
 				const accepted = value.split(',').map(v => v.trim());
 				filterPredicates.push(item => accepted.includes(String(item[key])));
-				continue;
+			} else {
+				filterPredicates.push(item => String(item[key]) === String(value));
 			}
-
-			filterPredicates.push(item => String(item[key]) === String(value));
 		}
 
 		const filtered = allItems.filter(item => {
-			// Apply all non-search filters
 			for (const predicate of filterPredicates) {
 				if (!predicate(item)) return false;
 			}
-
-			// Apply search if present
 			return !(searchQuery && !this.searchObject(item, searchQuery));
 		});
 
@@ -1120,37 +1137,50 @@
 		if (!Array.isArray(items)) items = Array.from(items);
 		if (items.length === 0) return items;
 
-		if (store.filters.orderby || store.filters.order) {
-			const orderby = store.filters.orderby || 'date';
-			const order = (store.filters.order || 'desc').toLowerCase();
+		const orderby = store.filters.orderby || 'date';
+		const order = (store.filters.order || 'desc').toLowerCase();
 
-			items.sort((a, b) => {
-				let aVal, bVal;
-
-				switch (orderby) {
-					case 'alphabetical':
-					case 'title':
-						aVal = (a.fields?.post_title || a.title || a.name || '').toLowerCase();
-						bVal = (b.fields?.post_title || b.title || b.name || '').toLowerCase();
-						break;
-					case 'modified':
-						aVal = new Date(a.modified || 0);
-						bVal = new Date(b.modified || 0);
-						break;
-					case 'date':
-					default:
-						aVal = new Date(a.date || 0);
-						bVal = new Date(b.date || 0);
-				}
-
-				if (aVal < bVal) return order === 'asc' ? -1 : 1;
-				if (aVal > bVal) return order === 'asc' ? 1 : -1;
-				return 0;
-			});
+		// Handle random ordering
+		if (['random', 'rand'].includes(orderby) || ['random', 'rand'].includes(order)) {
+			return this.shuffle(items);
 		}
+
+		items.sort((a, b) => {
+			let aVal, bVal;
+
+			switch (orderby) {
+				case 'alphabetical':
+				case 'title':
+					aVal = (a.title || a.name || '').toLowerCase();
+					bVal = (b.title || b.name || '').toLowerCase();
+					break;
+				case 'modified':
+					aVal = new Date(a.modified || a.date || 0);
+					bVal = new Date(b.modified || b.date || 0);
+					break;
+				case 'date':
+				default:
+					aVal = new Date(a.date || a.modified || 0);
+					bVal = new Date(b.date || b.modified || 0);
+			}
+
+			if (aVal < bVal) return order === 'asc' ? -1 : 1;
+			if (aVal > bVal) return order === 'asc' ? 1 : -1;
+			return 0;
+		});
+
 		return items;
 	}
 
+	shuffle(items) {
+		const array = items.slice();
+		for (let i = array.length - 1; i > 0; i--) {
+			const j = Math.floor(Math.random() * (i + 1));
+			[array[i], array[j]] = [array[j], array[i]];
+		}
+		return array;
+	}
+
 	searchObject(obj, search) {
 		if (!obj || typeof obj !== 'object') {
 			return typeof obj === 'string' && obj.toLowerCase().includes(search);
@@ -1201,23 +1231,22 @@
 				store.filters[key] = value;
 			}
 		});
-
 		this.notify(name, 'filters-changed', {
 			oldFilters,
 			filters: store.filters,
 			updates
 		});
 
-		this.notify(name, 'data-loaded', {
-			cached: true,
-			items: this.getFiltered(name)
-		});
-
 		const shouldFetch = await this.shouldFetchWithFilters(name, updates, oldFilters);
+
 		if (store.config.endpoint && shouldFetch) {
 			await this.fetch(name);
-		} else if (store.config.endpoint) {
-			this.notify(name, 'data-loaded');
+		} else {
+			const filtered = this.getFiltered(name);
+			this.notify(name, 'data-loaded', {
+				cached: true,
+				items: filtered
+			});
 		}
 	}
 

--
Gitblit v1.10.0