From a9dcdcde9453856c2c6c882c0400776e0ba4e4eb Mon Sep 17 00:00:00 2001 From: Alex Cabal Date: Sun, 28 Jan 2024 14:04:29 -0600 Subject: [PATCH] Limit artwork results at the DB level --- lib/Exceptions/PageOutOfBoundsException.php | 5 + lib/Formatter.php | 6 + lib/Library.php | 110 ++++++++---- www/artworks/index.php | 180 +++++++++++--------- 4 files changed, 190 insertions(+), 111 deletions(-) create mode 100644 lib/Exceptions/PageOutOfBoundsException.php diff --git a/lib/Exceptions/PageOutOfBoundsException.php b/lib/Exceptions/PageOutOfBoundsException.php new file mode 100644 index 00000000..25fa2ab5 --- /dev/null +++ b/lib/Exceptions/PageOutOfBoundsException.php @@ -0,0 +1,5 @@ + + * @return Array */ - public static function FilterArtwork(string $query = null, string $status = null, string $sort = null, int $submitterUserId = null): array{ + public static function FilterArtwork(string $query = null, string $status = null, string $sort = null, int $submitterUserId = null, int $page = 1, int $perPage = ARTWORK_PER_PAGE): array{ + // Returns an array of: + // ['artworks'] => Array, + // ['artworksCount'] => int + // // $status is either the string value of an ArtworkStatus enum, or one of these special statuses: // null: same as "all" // "all": Show all approved and in use artwork @@ -216,39 +220,85 @@ class Library{ // Remove diacritics and non-alphanumeric characters, but preserve apostrophes $query = trim(preg_replace('|[^a-zA-Z0-9\'’ ]|ius', ' ', Formatter::RemoveDiacritics($query ?? ''))); - // Split the query on word boundaries followed by spaces. This keeps words with apostrophes intact. - $tokenArray = preg_split('/\b\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); - - // Join the tokens with '|' to search on any token, but add word boundaries to force the full token to match - $tokenizedQuery = '\b(' . implode('|', $tokenArray) . ')\b'; - - $params[] = $tokenizedQuery; // art.Name - $params[] = $tokenizedQuery; // art.EbookUrl - $params[] = $tokenizedQuery; // a.Name - $params[] = $tokenizedQuery; // aan.Name - $params[] = $tokenizedQuery; // t.Name - // We use replace() below because if there's multiple contributors separated by an underscore, // the underscore won't count as word boundary and we won't get a match. // See https://github.com/standardebooks/web/pull/325 + $limit = $perPage; + $offset = (($page - 1) * $perPage); - $artworks = Db::Query(' - SELECT art.* - from Artworks art - inner join Artists a using (ArtistId) - left join ArtistAlternateNames aan using (ArtistId) - left join ArtworkTags at using (ArtworkId) - left join Tags t using (TagId) - where ' . $statusCondition . ' - and (art.Name regexp ? - or replace(art.EbookUrl, "_", " ") regexp ? - or a.Name regexp ? - or aan.Name regexp ? - or t.Name regexp ?) - group by art.ArtworkId - order by ' . $orderBy, $params, 'Artwork'); + if($query == ''){ + $artworksCount = Db::QueryInt(' + SELECT count(*) + from Artworks art + where ' . $statusCondition, $params); - return $artworks; + $params[] = $limit; + $params[] = $offset; + + $artworks = Db::Query(' + SELECT * + from Artworks art + where ' . $statusCondition . ' + limit ? + offset ?', $params, 'Artwork'); + } + else{ + // Split the query on word boundaries followed by spaces. This keeps words with apostrophes intact. + $tokenArray = preg_split('/\b\s+/', $query, -1, PREG_SPLIT_NO_EMPTY); + + // Join the tokens with '|' to search on any token, but add word boundaries to force the full token to match + $tokenizedQuery = '\b(' . implode('|', $tokenArray) . ')\b'; + + $params[] = $tokenizedQuery; // art.Name + $params[] = $tokenizedQuery; // art.EbookUrl + $params[] = $tokenizedQuery; // a.Name + $params[] = $tokenizedQuery; // aan.Name + $params[] = $tokenizedQuery; // t.Name + + $artworksCount = Db::QueryInt(' + SELECT + count(*) + from + (SELECT distinct + ArtworkId + from + Artworks art + inner join Artists a USING (ArtistId) + left join ArtistAlternateNames aan USING (ArtistId) + left join ArtworkTags at USING (ArtworkId) + left join Tags t USING (TagId) + where + ' . $statusCondition . ' + and (art.Name regexp ? + or replace(art.EbookUrl, "_", " ") regexp ? + or a.Name regexp ? + or aan.Name regexp ? + or t.Name regexp ?) + group by art.ArtworkId) x', $params); + + $params[] = $limit; + $params[] = $offset; + + $artworks = Db::Query(' + SELECT art.* + from Artworks art + inner join Artists a using (ArtistId) + left join ArtistAlternateNames aan using (ArtistId) + left join ArtworkTags at using (ArtworkId) + left join Tags t using (TagId) + where ' . $statusCondition . ' + and (art.Name regexp ? + or replace(art.EbookUrl, "_", " ") regexp ? + or a.Name regexp ? + or aan.Name regexp ? + or t.Name regexp ?) + group by art.ArtworkId + order by ' . $orderBy . ' + limit ? + offset ?', $params, 'Artwork'); + } + + return ['artworks' => $artworks, 'artworksCount' => $artworksCount]; } /** diff --git a/www/artworks/index.php b/www/artworks/index.php index 3b4031d9..465a54c1 100644 --- a/www/artworks/index.php +++ b/www/artworks/index.php @@ -1,9 +1,9 @@ Benefits?->CanReviewArtwork ?? false; $submitterUserId = $GLOBALS['User']?->Benefits?->CanUploadArtwork ? $GLOBALS['User']->UserId : null; $isSubmitterView = !$isReviewerView && $submitterUserId !== null; -if($page <= 0){ - $page = 1; -} +try{ + if($page <= 0){ + $page = 1; + } -if($perPage != ARTWORK_PER_PAGE && $perPage != 40 && $perPage != 80){ - $perPage = ARTWORK_PER_PAGE; -} + if($perPage != ARTWORK_PER_PAGE && $perPage != 40 && $perPage != 80){ + $perPage = ARTWORK_PER_PAGE; + } -// If we're passed string values that are the same as the defaults, -// set them to null so that we can have cleaner query strings in the navigation footer -if($sort !== null){ - $sort = mb_strtolower($sort); -} + // If we're passed string values that are the same as the defaults, + // set them to null so that we can have cleaner query strings in the navigation footer + if($sort !== null){ + $sort = mb_strtolower($sort); + } -if($sort == 'created-newest'){ - $sort = null; -} + if($sort == 'created-newest'){ + $sort = null; + } -if($isReviewerView){ - if($status == 'all' || $status === null){ - $filterArtworkStatus = 'all-admin'; + if($isReviewerView){ + if($status == 'all' || $status === null){ + $filterArtworkStatus = 'all-admin'; + } + } + + if($isSubmitterView){ + if($status == 'all' || $status === null){ + $filterArtworkStatus = 'all-submitter'; + } + if($status == 'unverified'){ + $filterArtworkStatus = 'unverified-submitter'; + } + } + + if(!$isReviewerView && !$isSubmitterView && !in_array($status, array('all', ArtworkStatus::Approved->value, 'in-use'))){ + $status = ArtworkStatus::Approved->value; + $filterArtworkStatus = $status; + } + + if($isReviewerView && !in_array($status, array('all', ArtworkStatus::Unverified->value, ArtworkStatus::Declined->value, ArtworkStatus::Approved->value, 'in-use')) + && !in_array($filterArtworkStatus, array('all-admin', ArtworkStatus::Unverified->value, ArtworkStatus::Declined->value, ArtworkStatus::Approved->value, 'in-use'))){ + $status = ArtworkStatus::Approved->value; + $filterArtworkStatus = $status; + } + + if($isSubmitterView && !in_array($status, array('all', ArtworkStatus::Unverified->value, ArtworkStatus::Approved->value, 'in-use')) + && !in_array($filterArtworkStatus, array('all-submitter', 'unverified-submitter', ArtworkStatus::Approved->value, 'in-use'))){ + $status = ArtworkStatus::Approved->value; + $filterArtworkStatus = $status; + } + + if($queryEbookUrl !== null){ + $artworks = Db::Query('SELECT * from Artworks where EbookUrl = ? and Status = ? limit 1', [$queryEbookUrl, ArtworkStatus::Approved], 'Artwork'); + $totalArtworkCount = sizeof($artworks); + } + else{ + $result = Library::FilterArtwork($query, $filterArtworkStatus, $sort, $submitterUserId, $page, $perPage); + $artworks = $result['artworks']; + $totalArtworkCount = $result['artworksCount']; + } + + $pageTitle = 'Browse Artwork'; + if($page > 1){ + $pageTitle .= ', page ' . $page; + } + + $pageDescription = 'Page ' . $page . ' of artwork'; + + $queryStringParams = []; + + if($query !== null && $query != ''){ + $queryStringParams['query'] = $query; + } + + if($status !== null){ + $queryStringParams['status'] = $status; + } + + if($sort !== null){ + $queryStringParams['sort'] = $sort; + } + + if($perPage !== ARTWORK_PER_PAGE){ + $queryStringParams['per-page'] = $perPage; + } + + $queryString = http_build_query($queryStringParams); + + $pages = ceil($totalArtworkCount / $perPage); + if($pages > 0 && $page > $pages){ + throw new Exceptions\PageOutOfBoundsException(); } } - -if($isSubmitterView){ - if($status == 'all' || $status === null){ - $filterArtworkStatus = 'all-submitter'; - } - if($status == 'unverified'){ - $filterArtworkStatus = 'unverified-submitter'; +catch(Exceptions\PageOutOfBoundsException){ + $url = '/artworks?page=' . $pages; + if($queryString != ''){ + $url .= '&' . $queryString; } + + header('Location: ' . $url); + exit(); } - -if(!$isReviewerView && !$isSubmitterView && !in_array($status, array('all', ArtworkStatus::Approved->value, 'in-use'))){ - $status = ArtworkStatus::Approved->value; - $filterArtworkStatus = $status; -} - -if($isReviewerView && !in_array($status, array('all', ArtworkStatus::Unverified->value, ArtworkStatus::Declined->value, ArtworkStatus::Approved->value, 'in-use')) - && !in_array($filterArtworkStatus, array('all-admin', ArtworkStatus::Unverified->value, ArtworkStatus::Declined->value, ArtworkStatus::Approved->value, 'in-use'))){ - $status = ArtworkStatus::Approved->value; - $filterArtworkStatus = $status; -} - -if($isSubmitterView && !in_array($status, array('all', ArtworkStatus::Unverified->value, ArtworkStatus::Approved->value, 'in-use')) - && !in_array($filterArtworkStatus, array('all-submitter', 'unverified-submitter', ArtworkStatus::Approved->value, 'in-use'))){ - $status = ArtworkStatus::Approved->value; - $filterArtworkStatus = $status; -} - -if($queryEbookUrl !== null){ - $artworks = Db::Query('SELECT * from Artworks where EbookUrl = ? and Status = ?', [$queryEbookUrl, ArtworkStatus::Approved], 'Artwork'); -} -else{ - $artworks = Library::FilterArtwork($query != '' ? $query : null, $filterArtworkStatus, $sort, $submitterUserId); -} - -$pageTitle = 'Browse Artwork'; -$pages = ceil(sizeof($artworks) / $perPage); -$totalArtworkCount = sizeof($artworks); -$artworks = array_slice($artworks, ($page - 1) * $perPage, $perPage); - -if($page > 1){ - $pageTitle .= ', page ' . $page; -} - -$pageDescription = 'Page ' . $page . ' of artwork'; - -if($query != ''){ - $queryString .= '&query=' . urlencode($query); -} - -if($status !== null){ - $queryString .= '&status=' . urlencode($status); -} - -if($sort !== null){ - $queryString .= '&sort=' . urlencode($sort); -} - -if($perPage !== ARTWORK_PER_PAGE){ - $queryString .= '&per-page=' . urlencode((string)$perPage); -} - ?> $pageTitle, 'artwork' => true, 'description' => $pageDescription]) ?>
@@ -153,13 +171,13 @@ if($perPage !== ARTWORK_PER_PAGE){ 0){ ?>