diff --git a/sql/incoming-schema-changes.sql b/sql/incoming-schema-changes.sql index 1670a572..14cb8332 100644 --- a/sql/incoming-schema-changes.sql +++ b/sql/incoming-schema-changes.sql @@ -1,3 +1,110 @@ USE ifdb; -- use this script for pending changes to the production DB schema + + + +-- Add column for game search filter to the users table + +ALTER TABLE `users` ADD COLUMN `game_filter` VARCHAR(150) DEFAULT ''; + + + + + +-- Add a view to track the news items for each game. +-- "G" in the news table means the news is about a game, +-- and "A" means the news has not been deleted. +-- Do not include versions of news items that have been +-- superseded by later edits. + +CREATE VIEW `recentgamenews` AS + SELECT + newsid AS news_id, + sourceid AS game_id, + created AS news_create_date + FROM news + WHERE source = 'G' + AND status = 'A' + AND newsid NOT IN ( + SELECT supersedes + FROM news + WHERE supersedes IS NOT NULL + ) + ORDER BY news_id DESC; + + + +-- Create a materialized view to store the data from the recentgamenews view + +CREATE TABLE recentgamenews_mv ( + news_id BIGINT(20) unsigned NOT NULL, + game_id VARCHAR(32) NOT NULL, + news_create_date DATETIME NOT NULL, + PRIMARY KEY (news_id), + KEY (game_id) +); + + + +-- Populate the recentgamenews_mv materialized view from the recentgamenews view + +lock tables recentgamenews_mv write, recentgamenews read; +truncate table recentgamenews_mv; +insert into recentgamenews_mv select * from recentgamenews; +unlock tables; + + + +-- Procedure to update one row of the recentgamenews_mv materialized view + +DROP PROCEDURE IF EXISTS refresh_recentgamenews_mv; +DELIMITER $$ + +CREATE PROCEDURE refresh_recentgamenews_mv ( + IN new_newsid BIGINT(20) +) +BEGIN +select * +from recentgamenews +where news_id = new_newsid +into @news_id, + @game_id, + @news_create_date; +if @news_id is null then + delete from recentgamenews_mv where news_id = new_news_id; +else +insert into recentgamenews_mv +values ( + @news_id, + @game_id, + @news_create_date + ) on duplicate key +update news_id = @news_id, + game_id = @game_id, + news_create_date = @news_create_date; +END IF; +END; +$$ + +DELIMITER ; + + + +-- Create triggers so that when a news item for a game is added to the news table, +-- the latest news date and the latest news id for that game will automatically +-- be updated in the recentgamenews_mv materialized view. [Question: How do I limit this to just game news?] + +CREATE TRIGGER recentgamenews_insert +AFTER INSERT ON news FOR EACH ROW +call refresh_recentgamenews_mv(NEW.newsid); + + +CREATE TRIGGER recentgamenews_update +AFTER UPDATE ON news FOR EACH ROW +call refresh_recentgamenews_mv(NEW.newsid); + + +CREATE TRIGGER recentgamenews_delete +AFTER DELETE ON news FOR EACH ROW +call refresh_recentgamenews_mv(OLD.newsid); diff --git a/sql/patch-full-schema.sql b/sql/patch-full-schema.sql index 40fae266..ebf2e98a 100644 --- a/sql/patch-full-schema.sql +++ b/sql/patch-full-schema.sql @@ -1,2 +1,449 @@ USE ifdb; +ALTER TABLE `users` ADD COLUMN `game_filter` VARCHAR(150) DEFAULT ''; + +-- +-- Final view structure for view `gameRatingsSandbox0` +-- + +/*!50001 DROP TABLE IF EXISTS `gameRatingsSandbox0`*/; +/*!50001 DROP VIEW IF EXISTS `gameRatingsSandbox0`*/; +/*!50001 SET @saved_cs_client = @@character_set_client */; +/*!50001 SET @saved_cs_results = @@character_set_results */; +/*!50001 SET @saved_col_connection = @@collation_connection */; +/*!50001 SET character_set_client = utf8 */; +/*!50001 SET character_set_results = utf8 */; +/*!50001 SET collation_connection = utf8_general_ci */; +/*!50001 CREATE ALGORITHM=UNDEFINED */ +/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */ +/*!50001 VIEW `gameRatingsSandbox0` AS +select `averaged`.`gameid` AS `gameid`, + `averaged`.`rated1` AS `rated1`, + `averaged`.`rated2` AS `rated2`, + `averaged`.`rated3` AS `rated3`, + `averaged`.`rated4` AS `rated4`, + `averaged`.`rated5` AS `rated5`, + `averaged`.`numRatingsInAvg` AS `numRatingsInAvg`, + `averaged`.`numRatingsTotal` AS `numRatingsTotal`, + `averaged`.`numMemberReviews` AS `numMemberReviews`, + `averaged`.`lastReviewDate` AS `lastReviewDate`, + `averaged`.`avgRating` AS `avgRating`, + pow( + ( + pow(1 - `averaged`.`avgRating`, 2) * `averaged`.`rated1` + + pow(2 - `averaged`.`avgRating`, 2) * `averaged`.`rated2` + + pow(3 - `averaged`.`avgRating`, 2) * `averaged`.`rated3` + + pow(4 - `averaged`.`avgRating`, 2) * `averaged`.`rated4` + + pow(5 - `averaged`.`avgRating`, 2) * `averaged`.`rated5` + ) / `averaged`.`numRatingsInAvg`, + 0.5 + ) AS `stdDevRating`, +( + 5 * (`averaged`.`rated5` + 1) + + 4 * (`averaged`.`rated4` + 1) + + 3 * (`averaged`.`rated3` + 1) + + 2 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`) - 1.65 * sqrt( + ( + ( + 25 * (`averaged`.`rated5` + 1) + + 16 * (`averaged`.`rated4` + 1) + + 9 * (`averaged`.`rated3` + 1) + + 4 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`) - pow( + ( + 5 * (`averaged`.`rated5` + 1) + + 4 * (`averaged`.`rated4` + 1) + + 3 * (`averaged`.`rated3` + 1) + + 2 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`), + 2 + ) + ) / (6 + `averaged`.`numRatingsInAvg`) + ) AS `starsort` +from ( + select `rating_counts`.`gameid` AS `gameid`, + `rating_counts`.`rated1` AS `rated1`, + `rating_counts`.`rated2` AS `rated2`, + `rating_counts`.`rated3` AS `rated3`, + `rating_counts`.`rated4` AS `rated4`, + `rating_counts`.`rated5` AS `rated5`, + `rating_counts`.`numRatingsInAvg` AS `numRatingsInAvg`, + `rating_counts`.`numRatingsTotal` AS `numRatingsTotal`, + `rating_counts`.`numMemberReviews` AS `numMemberReviews`, + `rating_counts`.`lastReviewDate` AS `lastReviewDate`, +( + `rating_counts`.`rated1` + + `rating_counts`.`rated2` * 2 + + `rating_counts`.`rated3` * 3 + + `rating_counts`.`rated4` * 4 + + `rating_counts`.`rated5` * 5 + ) / `rating_counts`.`numRatingsInAvg` AS `avgRating` + from ( + select `grouped`.`gameid` AS `gameid`, + sum( + case + when `grouped`.`rating` = 1 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated1`, + sum( + case + when `grouped`.`rating` = 2 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated2`, + sum( + case + when `grouped`.`rating` = 3 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated3`, + sum( + case + when `grouped`.`rating` = 4 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated4`, + sum( + case + when `grouped`.`rating` = 5 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated5`, + sum( + case + when `grouped`.`rating` in (1, 2, 3, 4, 5) + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `numRatingsInAvg`, + sum( + case + when `grouped`.`rating` in (1, 2, 3, 4, 5) then `grouped`.`count` + else 0 + end + ) AS `numRatingsTotal`, + sum( + case + when `grouped`.`hasReview` then `grouped`.`count` + else 0 + end + ) AS `numMemberReviews`, + max( + case + when `grouped`.`hasReview` then `grouped`.`lastRatingOrReviewDate` + else null + end + ) AS `lastReviewDate` + from ( + select count(`ifdb`.`reviews`.`id`) AS `count`, + `ifdb`.`reviews`.`rating` AS `rating`, + `ifdb`.`games`.`id` AS `gameid`, + ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2 AS `omitted`, + `ifdb`.`reviews`.`review` is not null AS `hasReview`, + max(ifnull(embargodate, createdate)) AS `lastRatingOrReviewDate` + from ( + `ifdb`.`games` + left outer join `ifdb`.`reviews` on ( + `ifdb`.`games`.`id` = `ifdb`.`reviews`.`gameid` + and ifnull( + current_timestamp() > `ifdb`.`reviews`.`embargodate`, + 1 + ) + and `ifdb`.`reviews`.`special` is null + and `ifdb`.`reviews`.`userid` not in (select `ifdb`.`users`.`id` from `ifdb`.`users` where `ifdb`.`users`.`Sandbox` = 1) + ) + ) + group by `ifdb`.`reviews`.`rating`, + `ifdb`.`games`.`id`, + ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2, + ifnull( + `ifdb`.`reviews`.`special`, + `ifdb`.`reviews`.`review` + ) is not null + ) `grouped` + group by `grouped`.`gameid` + ) `rating_counts` + ) `averaged` +*/; +/*!50001 SET character_set_client = @saved_cs_client */; +/*!50001 SET character_set_results = @saved_cs_results */; +/*!50001 SET collation_connection = @saved_col_connection */; + +-- +-- Final view structure for view `gameRatingsSandbox01` +-- + +/*!50001 DROP TABLE IF EXISTS `gameRatingsSandbox01`*/; +/*!50001 DROP VIEW IF EXISTS `gameRatingsSandbox01`*/; +/*!50001 SET @saved_cs_client = @@character_set_client */; +/*!50001 SET @saved_cs_results = @@character_set_results */; +/*!50001 SET @saved_col_connection = @@collation_connection */; +/*!50001 SET character_set_client = utf8 */; +/*!50001 SET character_set_results = utf8 */; +/*!50001 SET collation_connection = utf8_general_ci */; +/*!50001 CREATE ALGORITHM=UNDEFINED */ +/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */ +/*!50001 VIEW `gameRatingsSandbox01` AS +select `averaged`.`gameid` AS `gameid`, + `averaged`.`rated1` AS `rated1`, + `averaged`.`rated2` AS `rated2`, + `averaged`.`rated3` AS `rated3`, + `averaged`.`rated4` AS `rated4`, + `averaged`.`rated5` AS `rated5`, + `averaged`.`numRatingsInAvg` AS `numRatingsInAvg`, + `averaged`.`numRatingsTotal` AS `numRatingsTotal`, + `averaged`.`numMemberReviews` AS `numMemberReviews`, + `averaged`.`lastReviewDate` AS `lastReviewDate`, + `averaged`.`avgRating` AS `avgRating`, + pow( + ( + pow(1 - `averaged`.`avgRating`, 2) * `averaged`.`rated1` + + pow(2 - `averaged`.`avgRating`, 2) * `averaged`.`rated2` + + pow(3 - `averaged`.`avgRating`, 2) * `averaged`.`rated3` + + pow(4 - `averaged`.`avgRating`, 2) * `averaged`.`rated4` + + pow(5 - `averaged`.`avgRating`, 2) * `averaged`.`rated5` + ) / `averaged`.`numRatingsInAvg`, + 0.5 + ) AS `stdDevRating`, +( + 5 * (`averaged`.`rated5` + 1) + + 4 * (`averaged`.`rated4` + 1) + + 3 * (`averaged`.`rated3` + 1) + + 2 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`) - 1.65 * sqrt( + ( + ( + 25 * (`averaged`.`rated5` + 1) + + 16 * (`averaged`.`rated4` + 1) + + 9 * (`averaged`.`rated3` + 1) + + 4 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`) - pow( + ( + 5 * (`averaged`.`rated5` + 1) + + 4 * (`averaged`.`rated4` + 1) + + 3 * (`averaged`.`rated3` + 1) + + 2 * (`averaged`.`rated2` + 1) + + 1 * (`averaged`.`rated1` + 1) + ) / (5 + `averaged`.`numRatingsInAvg`), + 2 + ) + ) / (6 + `averaged`.`numRatingsInAvg`) + ) AS `starsort` +from ( + select `rating_counts`.`gameid` AS `gameid`, + `rating_counts`.`rated1` AS `rated1`, + `rating_counts`.`rated2` AS `rated2`, + `rating_counts`.`rated3` AS `rated3`, + `rating_counts`.`rated4` AS `rated4`, + `rating_counts`.`rated5` AS `rated5`, + `rating_counts`.`numRatingsInAvg` AS `numRatingsInAvg`, + `rating_counts`.`numRatingsTotal` AS `numRatingsTotal`, + `rating_counts`.`numMemberReviews` AS `numMemberReviews`, + `rating_counts`.`lastReviewDate` AS `lastReviewDate`, +( + `rating_counts`.`rated1` + + `rating_counts`.`rated2` * 2 + + `rating_counts`.`rated3` * 3 + + `rating_counts`.`rated4` * 4 + + `rating_counts`.`rated5` * 5 + ) / `rating_counts`.`numRatingsInAvg` AS `avgRating` + from ( + select `grouped`.`gameid` AS `gameid`, + sum( + case + when `grouped`.`rating` = 1 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated1`, + sum( + case + when `grouped`.`rating` = 2 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated2`, + sum( + case + when `grouped`.`rating` = 3 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated3`, + sum( + case + when `grouped`.`rating` = 4 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated4`, + sum( + case + when `grouped`.`rating` = 5 + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `rated5`, + sum( + case + when `grouped`.`rating` in (1, 2, 3, 4, 5) + and `grouped`.`omitted` = 0 then `grouped`.`count` + else 0 + end + ) AS `numRatingsInAvg`, + sum( + case + when `grouped`.`rating` in (1, 2, 3, 4, 5) then `grouped`.`count` + else 0 + end + ) AS `numRatingsTotal`, + sum( + case + when `grouped`.`hasReview` then `grouped`.`count` + else 0 + end + ) AS `numMemberReviews`, + max( + case + when `grouped`.`hasReview` then `grouped`.`lastRatingOrReviewDate` + else null + end + ) AS `lastReviewDate` + from ( + select count(`ifdb`.`reviews`.`id`) AS `count`, + `ifdb`.`reviews`.`rating` AS `rating`, + `ifdb`.`games`.`id` AS `gameid`, + ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2 AS `omitted`, + `ifdb`.`reviews`.`review` is not null AS `hasReview`, + max(ifnull(embargodate, createdate)) AS `lastRatingOrReviewDate` + from ( + `ifdb`.`games` + left outer join `ifdb`.`reviews` on ( + `ifdb`.`games`.`id` = `ifdb`.`reviews`.`gameid` + and ifnull( + current_timestamp() > `ifdb`.`reviews`.`embargodate`, + 1 + ) + and `ifdb`.`reviews`.`special` is null + ) + ) + group by `ifdb`.`reviews`.`rating`, + `ifdb`.`games`.`id`, + ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2, + ifnull( + `ifdb`.`reviews`.`special`, + `ifdb`.`reviews`.`review` + ) is not null + ) `grouped` + group by `grouped`.`gameid` + ) `rating_counts` + ) `averaged` +*/; +/*!50001 SET character_set_client = @saved_cs_client */; +/*!50001 SET character_set_results = @saved_cs_results */; +/*!50001 SET collation_connection = @saved_col_connection */; + +drop table if exists gameRatingsSandbox0_mv; +create table gameRatingsSandbox0_mv ( + `gameid` varchar(32) NOT NULL DEFAULT '', + `rated1` int unsigned, + `rated2` int unsigned, + `rated3` int unsigned, + `rated4` int unsigned, + `rated5` int unsigned, + `numRatingsInAvg` int unsigned, + `numRatingsTotal` int unsigned, + `numMemberReviews` int unsigned, + `lastReviewDate` datetime NOT NULL, + `avgRating` double, + `stdDevRating` double, + `starsort` double, + `updated` date, + PRIMARY KEY (`gameid`), + KEY `numRatingsInAvg` (`numRatingsInAvg`), + KEY `numRatingsTotal` (`numRatingsTotal`), + KEY `numMemberReviews` (`numMemberReviews`), + KEY `avgRating` (`avgRating`), + KEY `stdDevRating` (`stdDevRating`), + KEY `starsort` (`starsort`), + KEY `lastReviewDate` (`lastReviewDate`) +) ENGINE = MyISAM DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +lock tables gameRatingsSandbox0_mv write, gameRatingsSandbox0 read; +truncate table gameRatingsSandbox0_mv; +insert into gameRatingsSandbox0_mv select *, now() from gameRatingsSandbox0; +unlock tables; + +DROP PROCEDURE IF EXISTS refresh_gameRatingsSandbox0_mv; +DELIMITER $$ + +CREATE PROCEDURE refresh_gameRatingsSandbox0_mv ( + IN new_gameid varchar(32) +) +BEGIN +select * +from gameRatingsSandbox0 +where gameid = new_gameid into @gameid, + @rated1, + @rated2, + @rated3, + @rated4, + @rated5, + @numRatingsInAvg, + @numRatingsTotal, + @numMemberReviews, + @lastReviewDate, + @avgRating, + @stdDevRating, + @starsort; +if @gameid is null then + delete from gameRatingsSandbox0_mv where gameid = new_gameid; +else +insert into gameRatingsSandbox0_mv +values ( + @gameid, + @rated1, + @rated2, + @rated3, + @rated4, + @rated5, + @numRatingsInAvg, + @numRatingsTotal, + @numMemberReviews, + @lastReviewDate, + @avgRating, + @stdDevRating, + @starsort, + now() + ) on duplicate key +update gameid = @gameid, + rated1 = @rated1, + rated2 = @rated2, + rated3 = @rated3, + rated4 = @rated4, + rated5 = @rated5, + numRatingsInAvg = @numRatingsInAvg, + numRatingsTotal = @numRatingsTotal, + numMemberReviews = @numMemberReviews, + lastReviewDate = @lastReviewDate, + avgRating = @avgRating, + stdDevRating = @stdDevRating, + starsort = @starsort, + updated = now(); +END IF; +END; +$$ + +DELIMITER ; diff --git a/sql/unscrub-ifarchive.sql b/sql/unscrub-ifarchive.sql index 2fe363e8..81b9244b 100644 --- a/sql/unscrub-ifarchive.sql +++ b/sql/unscrub-ifarchive.sql @@ -311,6 +311,7 @@ select `averaged`.`gameid` AS `gameid`, `averaged`.`numRatingsInAvg` AS `numRatingsInAvg`, `averaged`.`numRatingsTotal` AS `numRatingsTotal`, `averaged`.`numMemberReviews` AS `numMemberReviews`, + `averaged`.`lastReviewDate` AS `lastReviewDate`, `averaged`.`avgRating` AS `avgRating`, pow( ( @@ -358,6 +359,7 @@ from ( `rating_counts`.`numRatingsInAvg` AS `numRatingsInAvg`, `rating_counts`.`numRatingsTotal` AS `numRatingsTotal`, `rating_counts`.`numMemberReviews` AS `numMemberReviews`, + `rating_counts`.`lastReviewDate` AS `lastReviewDate`, ( `rating_counts`.`rated1` + `rating_counts`.`rated2` * 2 @@ -420,13 +422,20 @@ from ( when `grouped`.`hasReview` then `grouped`.`count` else 0 end - ) AS `numMemberReviews` + ) AS `numMemberReviews`, + max( + case + when `grouped`.`hasReview` then `grouped`.`lastRatingOrReviewDate` + else null + end + ) AS `lastReviewDate` from ( select count(`ifdb`.`reviews`.`id`) AS `count`, `ifdb`.`reviews`.`rating` AS `rating`, `ifdb`.`games`.`id` AS `gameid`, ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2 AS `omitted`, - `ifdb`.`reviews`.`review` is not null AS `hasReview` + `ifdb`.`reviews`.`review` is not null AS `hasReview`, + max(ifnull(embargodate, createdate)) AS `lastRatingOrReviewDate` from ( `ifdb`.`games` left outer join `ifdb`.`reviews` on ( @@ -479,6 +488,7 @@ select `averaged`.`gameid` AS `gameid`, `averaged`.`numRatingsInAvg` AS `numRatingsInAvg`, `averaged`.`numRatingsTotal` AS `numRatingsTotal`, `averaged`.`numMemberReviews` AS `numMemberReviews`, + `averaged`.`lastReviewDate` AS `lastReviewDate`, `averaged`.`avgRating` AS `avgRating`, pow( ( @@ -526,6 +536,7 @@ from ( `rating_counts`.`numRatingsInAvg` AS `numRatingsInAvg`, `rating_counts`.`numRatingsTotal` AS `numRatingsTotal`, `rating_counts`.`numMemberReviews` AS `numMemberReviews`, + `rating_counts`.`lastReviewDate` AS `lastReviewDate`, ( `rating_counts`.`rated1` + `rating_counts`.`rated2` * 2 @@ -588,13 +599,20 @@ from ( when `grouped`.`hasReview` then `grouped`.`count` else 0 end - ) AS `numMemberReviews` + ) AS `numMemberReviews`, + max( + case + when `grouped`.`hasReview` then `grouped`.`lastRatingOrReviewDate` + else null + end + ) AS `lastReviewDate` from ( select count(`ifdb`.`reviews`.`id`) AS `count`, `ifdb`.`reviews`.`rating` AS `rating`, `ifdb`.`games`.`id` AS `gameid`, ifnull(`ifdb`.`reviews`.`RFlags`, 0) & 2 AS `omitted`, - `ifdb`.`reviews`.`review` is not null AS `hasReview` + `ifdb`.`reviews`.`review` is not null AS `hasReview`, + max(ifnull(embargodate, createdate)) AS `lastRatingOrReviewDate` from ( `ifdb`.`games` left outer join `ifdb`.`reviews` on ( @@ -874,6 +892,7 @@ create table gameRatingsSandbox0_mv ( `numRatingsInAvg` int unsigned, `numRatingsTotal` int unsigned, `numMemberReviews` int unsigned, + `lastReviewDate` datetime NOT NULL, `avgRating` double, `stdDevRating` double, `starsort` double, @@ -884,7 +903,8 @@ create table gameRatingsSandbox0_mv ( KEY `numMemberReviews` (`numMemberReviews`), KEY `avgRating` (`avgRating`), KEY `stdDevRating` (`stdDevRating`), - KEY `starsort` (`starsort`) + KEY `starsort` (`starsort`), + KEY `lastReviewDate` (`lastReviewDate`) ) ENGINE = MyISAM DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; lock tables gameRatingsSandbox0_mv write, gameRatingsSandbox0 read; @@ -910,6 +930,7 @@ where gameid = new_gameid into @gameid, @numRatingsInAvg, @numRatingsTotal, @numMemberReviews, + @lastReviewDate, @avgRating, @stdDevRating, @starsort; @@ -927,6 +948,7 @@ values ( @numRatingsInAvg, @numRatingsTotal, @numMemberReviews, + @lastReviewDate, @avgRating, @stdDevRating, @starsort, @@ -941,6 +963,7 @@ update gameid = @gameid, numRatingsInAvg = @numRatingsInAvg, numRatingsTotal = @numRatingsTotal, numMemberReviews = @numMemberReviews, + lastReviewDate = @lastReviewDate, avgRating = @avgRating, stdDevRating = @stdDevRating, starsort = @starsort, diff --git a/www/allnew b/www/allnew index 10b47e0d..25593971 100644 --- a/www/allnew +++ b/www/allnew @@ -17,13 +17,14 @@ if ($pg < 1) $pg = 1; $showFlagged = isset($_GET['showFlagged']) && $_GET['showFlagged']; +$override_game_filter = get_req_data('nogamefilter'); // calculate where that puts us in the results $firstOnPage = ($pg - 1) * PER_PAGE; $lastOnPage = $firstOnPage + PER_PAGE - 1; // query the items -$items = getNewItems($db, $lastOnPage + 1, $type); +list($items, $game_filter_was_applied) = getNewItems($db, $lastOnPage + 1, $type, $override_game_filter); $tot = count($items); $params = []; @@ -31,8 +32,12 @@ if ($showFlagged) $params['showFlagged'] = 1; if ($reviews) $params['reviews'] = ""; // set up the page controls +$game_filter_parameter = ""; +if ($override_game_filter) { + $game_filter_parameter = "&nogamefilter=1"; +} $pageCtl = "" - . makePageControl("allnew?" . http_build_query($params), $pg, $pg + ($tot > PER_PAGE ? 1 : 0), + . makePageControl("allnew?" . http_build_query($params) . $game_filter_parameter, $pg, $pg + ($tot > PER_PAGE ? 1 : 0), $firstOnPage, $lastOnPage, -1, false, false, false) . ""; @@ -53,6 +58,11 @@ showNewItems($db, $firstOnPage, $lastOnPage, $items, ['showFlagged' => $showFlag echo "


$pageCtl
\n"; echo ""; // prerender-moderate +if ($game_filter_was_applied) { + $games_filtered_announcement = writeGamesFilteredAnnouncement("all_new_reviews", null, null); + echo "

" . $games_filtered_announcement . "

"; +} + // end the page pageFooter(); diff --git a/www/components/competitions.php b/www/components/competitions.php index 7b8ae1fe..f9e11767 100644 --- a/www/components/competitions.php +++ b/www/components/competitions.php @@ -9,7 +9,7 @@ Browse competitions | Search competitions -

\ No newline at end of file +

diff --git a/www/components/games.php b/www/components/games.php index fd3d1ae2..330f85ce 100644 --- a/www/components/games.php +++ b/www/components/games.php @@ -17,7 +17,7 @@ define("ENABLE_IMAGES", 1); // get the latest games and game news -$items = getNewItems($db, 6, NEWITEMS_GAMES | NEWITEMS_GAMENEWS); +list($items, $game_filter_was_applied) = getNewItems($db, 6, NEWITEMS_GAMES | NEWITEMS_GAMENEWS); // show the items $totcnt = count($items); diff --git a/www/components/ifdb-recommends.php b/www/components/ifdb-recommends.php index 5350728f..a17232a9 100644 --- a/www/components/ifdb-recommends.php +++ b/www/components/ifdb-recommends.php @@ -22,12 +22,13 @@ function sortBySortorder($a, $b) $limit = "limit 0, $maxpicks"; $browse = 1; $count_all_possible_rows = false; +$override_game_filter = 0; // run the search for highly-rated games list($recs, $rowcnt, $sortList, $errMsg, $summaryDesc, $badges, - $specials, $specialsUsed, $orderBy) = - doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_possible_rows); + $specials, $specialsUsed, $orderBy, $games_were_filtered) = + doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_possible_rows, $override_game_filter); $recs = array_values(array_filter($recs, function($r) { $buried = $r['flags'] & FLAG_SHOULD_HIDE; diff --git a/www/components/recommended-lists.php b/www/components/recommended-lists.php index 9ea9e703..15acbf7c 100644 --- a/www/components/recommended-lists.php +++ b/www/components/recommended-lists.php @@ -8,7 +8,7 @@ Browse lists | Search lists -

\ No newline at end of file +

diff --git a/www/components/reviews.php b/www/components/reviews.php index 89c591fd..03f70baf 100644 --- a/www/components/reviews.php +++ b/www/components/reviews.php @@ -1,7 +1,7 @@

Reviews

" @@ -103,6 +104,7 @@ function showHiddenFields() . "" . "" . "" + . "" . "" . " noexedownloads = '$qNoExes', publiclists = '$qPublicLists', gender = '$qGender', emailflags = '$emailflags', offsite_display = '$qOffsite', - accessibility = '$qAccessibility' + accessibility = '$qAccessibility', + game_filter = '$qgamefilter' $setPic where id = '$usernum'", $db) == false) { $errFlagged = "An error occurred updating the database. You might @@ -924,6 +931,32 @@ captchaSupportScripts($captchaKey); +

5. Game Filtering

+ +
+

To see certain kinds of games less often, + you can create a filter using IFDB's + search prefixes. For + instance, to hide games in a certain language, games in a + certain genre, and games with a certain tag, you can type +

-language:example -genre:example -tag:example

+ (replacing "example" with the name of the language, genre, or + tag). While you are logged in, your saved filter + will be used to hide games, but only on certain pages: the home page, + the "New Reviews on IFDB" page, and the search/browse page + (where the filter will automatically be added to the end of your + search terms). The "New Reviews on IFDB" page and the search/browse + page will show an option to temporarily turn off the filter.

+

Game information on IFDB is incomplete + and can have mistakes, so a filter is not a reliable + way to make sure that games are kid-friendly. + +

+ + +

+ +

diff --git a/www/home b/www/home index 0378c2d5..1a31cd88 100644 --- a/www/home +++ b/www/home @@ -123,7 +123,7 @@ if ($debugflag) echo "debug mode enabled...
"; 1, 'lists_limit' => 2, 'polls_limit' => 2, @@ -137,7 +137,7 @@ if ($debugflag) echo "debug mode enabled...
"; 'enableImages' => false, ]); } else { - $items = getNewItems($db, 1, $itemTypes); + list($items, $game_filter_was_applied) = getNewItems($db, 1, $itemTypes); showNewItems($db, 0, 0, $items, [ 'allowHiddenBanner' => false, 'showDescriptions' => false, diff --git a/www/newitems.php b/www/newitems.php index 80e8cdb0..746ec450 100644 --- a/www/newitems.php +++ b/www/newitems.php @@ -22,7 +22,7 @@ | NEWITEMS_COMPNEWS ); -function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) +function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = [], $override_game_filter = 0) { $days = $options['days'] ?? null; @@ -39,6 +39,11 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) . "and filtertype = 'K') = 0"; } + $override_game_filter = get_req_data('nogamefilter'); + + // So far, we have not applied a custom game filter + $game_filter_was_applied = 0; + // Include only reviews from our sandbox or sandbox 0 (all users) $sandbox = "(0)"; if ($curuser) @@ -166,6 +171,22 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) if ($itemTypes & NEWITEMS_REVIEWS) { $reviews_limit = $options['reviews_limit'] ?? $limit; + $term = ""; + $searchType = "game"; + $sortby = "recently_reviewed"; + $games_limit_clause = "limit $reviews_limit"; + $browse = 0; + // If the user has a custom game filter and has not overridden it, that + // filter will automatically be applied within the doSearch function. + list($game_rows_after_filtering, $rowcnt, $sortList, $errMsg, $summaryDesc, + $badges, $specials, $specialsUsed, $orderBy, $game_filter_was_applied) + = doSearch($db, $term, $searchType, $sortby, $games_limit_clause, $browse); + // Note the gameids of games that we might want to display reviews for + foreach ($game_rows_after_filtering as $game_row) { + $gameids_after_filtering[] = "'" . $game_row['id'] . "'"; + } + $reviews_limit_clause = "limit $reviews_limit"; + // prepare to query reviews if ($days) $dayWhere = "greatest(reviews.createdate, ifnull(reviews.embargodate, '0000-00-00')) > date_sub(now(), interval $days day)"; // query the recent reviews (minus mutes) $anm = str_replace('#USERID#', 'reviews.userid', $andNotMuted); @@ -185,12 +206,11 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) games.flags from reviews - join games - join users + join games on games.id = reviews.gameid + join users on users.id = reviews.userid left outer join specialreviewers on specialreviewers.id = special where - games.id = reviews.gameid - and users.id = reviews.userid + gameid in (".join(",", $gameids_after_filtering).") and reviews.review is not null and ifnull(now() >= reviews.embargodate, 1) and ifnull(specialreviewers.code, '') <> 'external' @@ -198,7 +218,7 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) and $dayWhere $anm order by d desc, id desc - limit $reviews_limit", $db); + $reviews_limit_clause", $db); $revcnt = mysql_num_rows($result); for ($i = 0 ; $i < $revcnt ; $i++) { $row = mysql_fetch_array($result, MYSQL_ASSOC); @@ -229,15 +249,38 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) } if ($itemTypes & NEWITEMS_GAMENEWS) { + // Query the games and sort them so that games with recent news + // come first. If the user has a custom game filter and has not + // overridden it, that filter will automatically be applied within + // the doSearch function. + $term = ""; + $searchType = "game"; + $sortby = "recent_game_news"; $games_limit = $options['games_limit'] ?? $limit; + $limit_clause = "limit $games_limit"; + $browse = 0; + list($rows, $rowcnt, $sortList, $errMsg, $summaryDesc, $badges, + $specials, $specialsUsed, $orderBy) = + doSearch($db, $term, $searchType, $sortby, $limit_clause, $browse); + + // Put the resulting game IDs in an array so that we can + // limit news results to these (possibly filtered) games. + $games_with_recent_news = []; + foreach ($rows as $row) { + $games_with_recent_news[] = $row['id']; + } + // query game news updates queryNewNews( $items, $db, $games_limit, "G", - "join games as g on g.id = n.sourceid", + "join games as g on g.id = n.sourceid " + . "join recentgamenews_mv r on r.news_id = n.newsid", "g.id as sourceID, g.title as sourceTitle, " - . "(g.coverart is not null) as hasart, g.pagevsn", $days); + . "(g.coverart is not null) as hasart, g.pagevsn", + $days, $games_with_recent_news); } + if ($itemTypes & NEWITEMS_COMPNEWS) { $comps_limit = $options['comps_limit'] ?? $limit; // add the competition news updates @@ -251,7 +294,7 @@ function getNewItems($db, $limit, $itemTypes = NEWITEMS_ALLITEMS, $options = []) usort($items, "sortNewItemsByDate"); // return the item list - return $items; + return array($items, $game_filter_was_applied); } // sorting callback: sort from newest to oldest @@ -275,7 +318,8 @@ function sortNewItemsByDate($a, $b) } function queryNewNews(&$items, $db, $limit, $sourceType, - $sourceJoin, $sourceCols, $days = null) + $sourceJoin, $sourceCols, $days = null, + $games_with_recent_news = []) { // get the logged-in user checkPersistentLogin(); @@ -305,6 +349,13 @@ function queryNewNews(&$items, $db, $limit, $sourceType, if ($mysandbox != 0) $sandbox = "(0,$mysandbox)"; } + + // Include game news only from games that remain after applying a custom filter + $andGameIDsMatch = ""; + if ($games_with_recent_news) { + $games_with_recent_news = "'" . implode("', '",$games_with_recent_news) . "'"; + $andGameIDsMatch = "and g.id in ($games_with_recent_news) "; + } // query the data $result = mysql_query( @@ -333,6 +384,7 @@ function queryNewNews(&$items, $db, $limit, $sourceType, and uorig.sandbox in $sandbox and $dayWhere $andNotMuted + $andGameIDsMatch order by n.created desc limit $limit", $db); @@ -350,7 +402,7 @@ function showNewItems($db, $first, $last, $items, $options = []) $itemTypes = $options['itemTypes'] ?? NEWITEMS_ALLITEMS; // if the caller didn't provide the new item lists, query them if (!$items) - $items = getNewItems($db, $last, $itemTypes, $options); + list($items, $game_filter_was_applied) = getNewItems($db, $last, $itemTypes, $options); // show them showNewItemList($db, $first, $last, $items, $options); @@ -717,7 +769,7 @@ function showNewItemList($db, $first, $last, $items, $options) function showNewItemsRSS($db, $showcnt) { // query the new items - $items = getNewItems($db, $showcnt - 1); + list($items, $game_filter_was_applied) = getNewItems($db, $showcnt - 1); $totcnt = count($items); $lastBuildDate = false; diff --git a/www/review b/www/review index 11a24b3c..38e8577c 100644 --- a/www/review +++ b/www/review @@ -143,8 +143,9 @@ if (isset($_REQUEST['browse'])) { // run the search $count_all_possible_rows = false; + $override_game_filter = 0; list($rows, $rowcnt, $sortList, $errMsg) = - doSearch($db, $searchFor, "game", "rel", "limit 0, 10", false, $count_all_possible_rows); + doSearch($db, $searchFor, "game", "rel", "limit 0, 10", false, $count_all_possible_rows, $override_game_filter); // show the results $term = htmlspecialcharx($searchFor); diff --git a/www/search b/www/search index c3c97359..5d08ea0f 100644 --- a/www/search +++ b/www/search @@ -58,6 +58,7 @@ if ($api_mode && !isset($_SESSION['logged_in_as']) && isset($_SERVER['PHP_AUTH_U $term = get_req_data('searchfor'); $bTerm = get_req_data('searchbar'); $extraLink = get_req_data('xlink'); +$override_game_filter = get_req_data('nogamefilter'); if ($term == "" && $bTerm != "") $term = $bTerm; @@ -298,9 +299,9 @@ if ($term || $browse) { // run the search list($rows, $rowcnt, $sortList, $errMsg, $summaryDesc, $badges, - $specials, $specialsUsed, $orderBy) = + $specials, $specialsUsed, $orderBy, $games_were_filtered) = doSearch($db, $term, $searchType, $sortby, $limit, $browse, - $count_all_possible_rows); + $count_all_possible_rows, $override_game_filter); // adjust our page limits to include the whole result set if desired if ($pgAll) { @@ -309,7 +310,7 @@ if ($term || $browse) { } // if there's exactly one row, jump directly to the result page - if ($rowcnt == 1 && !$browse && !$api_mode) { + if ($rowcnt == 1 && !$browse && !$api_mode && !$games_were_filtered) { $redir = false; switch ($searchType) { case "game": @@ -1003,7 +1004,7 @@ if ($api_mode) { $item['coverArtLink'] = get_root_url() . "coverart?id=$id&version={$row['pagevsn']}"; } - if (isset($row['rounded_median_time_in_minutes'])) { + if ($row['rounded_median_time_in_minutes']) { $item['playTimeInMinutes'] = $row['rounded_median_time_in_minutes']; } @@ -1126,7 +1127,8 @@ else if ($term || $browse) . ($searchType == "poll" ? "&poll" : "") . ($searchType == "member" ? "&member" : "") . ($searchType == "comp" ? "&comp" : "") - . ($searchType == "tag" ? "&tag" : ""), + . ($searchType == "tag" ? "&tag" : "") + . ($override_game_filter ? "&nogamefilter=1" : ""), $pg, $lastPage, $firstOnPage, $lastOnPage, $rowcnt, true, $rowcnt < $showAllMaxRows, $pgAll) @@ -1138,6 +1140,17 @@ else if ($term || $browse) echo "No results were found."; + if ($games_were_filtered) { + $page = ""; + if ($browse) { + $page = "browse_games"; + } else { + $page = "search_games"; + } + $games_filtered_announcement = writeGamesFilteredAnnouncement($page, $sortby, $term); + echo '

' . $games_filtered_announcement . '

'; + } + if ($searchType == "game") { echo "
" . "

Can't find the game you're looking for?

" @@ -1569,6 +1582,18 @@ else if ($term || $browse) // add the page controls again at the bottom, if applicable if ($pg != 1 || $rowcnt > $perPage) echo "
$pageCtl
"; + + // Announce that the game results are filtered, if applicable + if ($games_were_filtered) { + $page = ""; + if ($browse) { + $page = "browse_games"; + } else { + $page = "search_games"; + } + $games_filtered_announcement = writeGamesFilteredAnnouncement($page, $sortby, $term); + echo '

' . $games_filtered_announcement . '

'; + } } diff --git a/www/searchutil.php b/www/searchutil.php index 1dcd988d..c10bef0a 100644 --- a/www/searchutil.php +++ b/www/searchutil.php @@ -43,7 +43,21 @@ function convertTimeStringToMinutes($h_m_string) { } -function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_possible_rows = false) +// Construct a message telling the user that the game results were filtered +function writeGamesFilteredAnnouncement($page, $sort_order, $search_term) { + $games_filtered_announcement = 'Your account is set up to use a game filter by default, and that filter was applied on this page. You can '; + if ($page == "search_games") { + $games_filtered_announcement .= 'search again without the filter.'; + } else if ($page == "browse_games") { + $games_filtered_announcement .= 'also browse without the filter.'; + } else if ($page == "all_new_reviews") { + $games_filtered_announcement .= 'also browse without the filter.'; + } + return $games_filtered_announcement; +} + + +function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_possible_rows = false, $override_game_filter = 0) { // we need the current user for some types of queries checkPersistentLogin(); @@ -73,6 +87,9 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ // assume no badge info $badges = false; + // So far, we haven't applied a custom game filter + $games_were_filtered = false; + // set up the parameters for the type of search we're performing if ($searchType == "list") { @@ -277,6 +294,7 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ stdDevRating as ratingdev, numRatingsTotal, numMemberReviews, + lastReviewDate, starsort, games.sort_title as sort_title, games.sort_author as sort_author, @@ -291,7 +309,21 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ $matchCols = "title, author, `desc`, tags"; $likeCol = "title"; $summaryDesc = "Games"; - } + + + // Handle custom game filters + if ($curuser && $override_game_filter != 1) { + // We're logged in, and haven't been told to override a custom game filter, so check for one + $result = mysqli_execute_query($db, "select game_filter from users where id = ?", [$curuser]); + if (!$result) throw new Exception("Error: " . mysqli_error($db)); + [$gameFilter] = mysql_fetch_row($result); + if ($gameFilter) { + // We've found a custom game filter, so add it to the end of the search term + $games_were_filtered = true; + $term .= " $gameFilter"; + } + } + } // parse the search for ($ofs = 0, $len = strlen($term), $words = array(), @@ -881,6 +913,8 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ 'old' => array('sort_pub,', 'Earliest Publication First'), 'long' => array('rounded_median_time_in_minutes desc, starsort desc,', 'Longest First'), 'short' => array('-rounded_median_time_in_minutes desc, starsort desc,', 'Shortest First'), + 'recently_reviewed' => array('lastReviewDate desc,', 'Recently Reviewed First'), + 'recent_game_news' => array('news_id desc,', 'Recent News First'), 'rand' => array('rand(),', 'Random Order')); if (count($words)) { $defSortBy = 'rel'; @@ -1002,6 +1036,15 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ } } + // If we're sorting by recent game news, select the news item's create date + // and join the recent game news + if ($searchType == "game" && $sortby == "recent_game_news") { + $selectList .= ", news_create_date"; + $tableList .= " left outer join recentgamenews_mv on games.id = recentgamenews_mv.game_id"; + } + + + // Build tags join $tagsJoin = ""; @@ -1065,20 +1108,17 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ } $sql_calc_found_rows = "sql_calc_found_rows"; - if ($searchType === "game" && !$term) { - // `sql_calc_found_rows` forces the query to ignore the `limit` clause in order to count all possible results. - // But when browsing for all games, we can do a fast `count(*)` query instead - $sql_calc_found_rows = ""; - } - if (!$count_all_possible_rows) { - // `sql_calc_found_rows` forces the query to ignore the `limit` clause - // in order to count all possible results, which means a slower full - // table scan. If the total number of rows is not needed, we can skip - // `sql_calc_found_rows` to speed up the query. + + if (($searchType === "game" && !$term) || !$count_all_possible_rows) { + // `sql_calc_found_rows` forces the query to ignore the `limit` clause in order to + // count all possible results, which means a full table scan, which can be slow. + // But if we're browsing all games, we can skip `sql_calc_found_rows` and do a fast + // `count(*)` query instead. If we're searching but we don't need the number of + // possible rows, we can skip the counting altogether. + $sql_calc_found_rows = ""; } - // build the SELECT statement $sql = "select $sql_calc_found_rows $selectList @@ -1147,7 +1187,7 @@ function doSearch($db, $term, $searchType, $sortby, $limit, $browse, $count_all_ // return the results return array($rows, $rowcnt, $sortList, $errMsg, $summaryDesc, - $badges, $specials, $specialsUsed, $orderBy); + $badges, $specials, $specialsUsed, $orderBy, $games_were_filtered); } ?>