diff --git a/db/000_tables.sql b/db/000_tables.sql
index 027ea89d..2115c794 100644
--- a/db/000_tables.sql
+++ b/db/000_tables.sql
@@ -261,8 +261,9 @@ CREATE TABLE IF NOT EXISTS `modReleases` (
`lastModified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`releaseId`),
-- This has to include identifier, as one mod can contain releases for multiple identifier's.
- -- This also has to include the modId, as tool/other mods dont need to have a identifier.
- UNIQUE INDEX `identifier` (`modId`, `identifier`, `version`),
+ -- This also has to include the modId, as tool/other mods don't need to have a identifier.
+ UNIQUE INDEX `hybrid_identifier` (`modId`, `identifier`, `version`),
+ INDEX `identifier` (`identifier`), -- For search by identifier
UNIQUE INDEX `assetid` (`assetId`),
INDEX `modid` (`modId`),
CONSTRAINT `FK_modReleases_assetId` FOREIGN KEY (`assetId`) REFERENCES `assets`(`assetId`) ON UPDATE CASCADE ON DELETE CASCADE,
diff --git a/db/134_migrate.sql b/db/134_migrate.sql
new file mode 100644
index 00000000..65d54274
--- /dev/null
+++ b/db/134_migrate.sql
@@ -0,0 +1,23 @@
+USE `moddb`;
+
+DELIMITER $$
+
+CREATE OR REPLACE PROCEDURE upgrade_database__moderation()
+BEGIN
+
+IF NOT EXISTS( (SELECT * FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='moddb' AND
+ TABLE_NAME='fmodReleases' AND COLUMN_NAME='identifier' and INDEX_NAME='hybrid_identifier') ) THEN
+
+ ALTER TABLE `modReleases` RENAME INDEX `identifier` TO `hybrid_identifier`;
+ ALTER TABLE `modReleases` ADD INDEX `identifier` (`identifier`); -- For search by identifier
+
+END IF;
+
+
+END $$
+
+CALL upgrade_database__moderation() $$
+
+DELIMITER ;
+
+
diff --git a/lib/core.php b/lib/core.php
index e0a556b0..40c992be 100644
--- a/lib/core.php
+++ b/lib/core.php
@@ -294,7 +294,7 @@ function createADOConnection($config, $persistent = true)
*/
function forceRedirectAfterPOST()
{
- header('Location: '.$_SERVER['REQUEST_URI'], true, 303);
+ header('Location: '.$_SERVER['REQUEST_URI'], true, HTTP_FOUND);
}
/** This function forces a location header to the provided location, as well as status 303.
@@ -305,7 +305,7 @@ function forceRedirectAfterPOST()
function forceRedirect($url)
{
if(gettype($url) === 'array') $url = buildLocalUri($url);
- header('Location: '.$url, true, 303);
+ header('Location: '.$url, true, HTTP_FOUND);
}
/** Constructs a uri from a parse_url result shaped array.
@@ -836,7 +836,8 @@ function maybeFormatDownloadTrackingUrlDependingOnFileExt($file)
}
const HTTP_CREATED = 201;
-const HTTP_FOUND = 302;
+const HTTP_FOUND = 302; // temp redirect
+const HTTP_SEE_OTHER = 303; // permanent redirect
const HTTP_BAD_REQUEST = 400;
const HTTP_UNAUTHORIZED = 401;
const HTTP_FORBIDDEN = 403;
diff --git a/list-mod.php b/list-mod.php
index c50dc60f..1da22b71 100644
--- a/list-mod.php
+++ b/list-mod.php
@@ -2,6 +2,25 @@
include($config["basepath"] . "lib/search-mods.php");
+// Mod ID search: identifier uniquely identifies a mod, so redirect directly to its page.
+if(!empty($_GET['i'])) {
+ $modid = trim($_GET['text']);
+ $retractionFilter = canModerate(null, $user) ? '' : 'AND mrr.releaseId IS NULL';
+ $mod = $con->getRow("
+ SELECT m.urlAlias, m.assetId
+ FROM modReleases mr
+ JOIN mods m ON m.modId = mr.modId
+ LEFT JOIN modReleaseRetractions mrr ON mrr.releaseId = mr.releaseId
+ WHERE mr.identifier = ?
+ $retractionFilter
+ LIMIT 1
+ ", [$modid]);
+
+ if($mod) exit(forceRedirect(formatModPath($mod)));
+ else addMessage(MSG_CLASS_WARN, "No mod found with identifier '$modid'.", true);
+ // if we failed to find the mod directly we drop into the normal search from here
+}
+
if(isset($_GET['paging'])) {
if($paramError = validateModSearchInputs($searchParams, true)) {
http_response_code(HTTP_BAD_REQUEST);
@@ -40,6 +59,7 @@
'gameversions' => !empty($filters['gameversions']) ? array_flip($filters['gameversions']) : [],
'tags' => [],
'stati' => !empty($filters['stati']) ? array_flip($filters['stati']) : [ STATUS_RELEASED => true, STATUS_LOCKED => true ],
+ 'searchId' => !empty($_GET['i']),
];
if(!empty($filters['tags'])) {
diff --git a/templates/list-mod.tpl b/templates/list-mod.tpl
index 3624491f..88b36ff1 100644
--- a/templates/list-mod.tpl
+++ b/templates/list-mod.tpl
@@ -8,12 +8,20 @@
-
+ {if $selectedParams['searchId']}
+
+ {else}
+
+ {/if}
+
+
+
+
-
-
+
{foreach from=$selectedParams['tags'] item=tag}
{/foreach}
@@ -30,14 +38,14 @@
-
+
{if !empty($selectedParams['contributor'])}{/if}
-
+
{foreach from=$majorGameVersions item=version}
@@ -46,7 +54,7 @@
-
+
{foreach from=$gameVersions item=version}
{/foreach}
@@ -55,7 +63,7 @@
{if $selectedParams['category'] !== 's'}
-
+
@@ -67,7 +75,7 @@
{/if}
-
+
@@ -77,7 +85,7 @@
{if canModerate(null, $user)}
-
+
@@ -156,6 +164,20 @@
attachRemoteSearchHandler(document.getElementById('tags-box'));
});
+
+ const searchBoxEl = document.getElementById('search-box');
+ const otherFilterEls = document.querySelectorAll('select[name="side"], #tags-box select, #contributor-box select, select[name="c"], select[name="t"], select[name="mv"], select[name="gv[]"], select[name="stati[]"]');
+ const modidToggleEl = document.querySelector('input[name="i"]');
+ modidToggleEl.addEventListener('change', function(e) \{
+ const active = e.target.checked;
+ searchBoxEl.dataset.label = active ? 'Exact ModID' : 'Text';
+ searchBoxEl.title = active ? 'Searches for a specific ModID.' : 'Searches mod names, summaries and descriptions.'
+ for(const el of otherFilterEls) \{
+ el.disabled = active;
+ $(el).trigger('chosen:updated');
+ }
+ });
+
let fetchCursor = '{$fetchCursorJS}';
const scrollTrigger = document.getElementById('scroll-trigger');
if(scrollTrigger) {