From 7d838b894d25c51ad59c0eee3359ca5944cb15ea Mon Sep 17 00:00:00 2001 From: Mark Noble Date: Wed, 27 Nov 2024 15:54:48 -0700 Subject: [PATCH] Year in Review Dynamically generate images for use in Year in Review --- .../themes/responsive/YearInReview/slide.tpl | 21 +--- code/web/release_notes/24.12.00.MD | 3 +- code/web/services/MyAccount/AJAX.php | 30 +++++ .../version_updates/24.12.00.php | 1 - .../sys/YearInReview/YearInReviewSetting.php | 108 +++++++++++++++++- code/web/year_in_review/2024.json | 106 ++++++----------- 6 files changed, 179 insertions(+), 90 deletions(-) diff --git a/code/web/interface/themes/responsive/YearInReview/slide.tpl b/code/web/interface/themes/responsive/YearInReview/slide.tpl index 7bc4fa7890..ecc91f907b 100644 --- a/code/web/interface/themes/responsive/YearInReview/slide.tpl +++ b/code/web/interface/themes/responsive/YearInReview/slide.tpl @@ -1,20 +1,9 @@ {strip}
-
- Year in review background -
- {foreach from=$slideInfo->overlay_text item=textInfo} -
- {$textInfo->text} -
- {/foreach} + {if empty($slideInfo->overlay_text)} + Year in review background + {else} + Year in review background + {/if}
{/strip} \ No newline at end of file diff --git a/code/web/release_notes/24.12.00.MD b/code/web/release_notes/24.12.00.MD index 88c424ac2c..c102b811c1 100644 --- a/code/web/release_notes/24.12.00.MD +++ b/code/web/release_notes/24.12.00.MD @@ -15,7 +15,8 @@ ### Indexing Updates - Add a new format for Tonies based on a publisher (260b, 264b, 710a) containing Boxine and a title (245a) containing Tonie. (*MDN*) - Add a new format for Yoto based on a publisher (260b, 264b, 710a) containing Yoto and a title (245a) containing Yoto. (*MDN*) -- When filtering formats, if Zines is active, discard other formats. (*MDN*) +- When filtering formats, if Zines is active, discard other formats. (DIS-56) (*MDN*) +- When checking for the Zines format, ignore trailing punctuation. (DIS-56) (*MDN*) ### Local ILL (DIS-34) - Add new settings to configure the Local ILL system in use. (*MDN*) diff --git a/code/web/services/MyAccount/AJAX.php b/code/web/services/MyAccount/AJAX.php index 33229e6bd4..67e00864bc 100644 --- a/code/web/services/MyAccount/AJAX.php +++ b/code/web/services/MyAccount/AJAX.php @@ -8743,4 +8743,34 @@ function getYearInReviewSlide() : array { return $result; } + + function getYearInReviewSlideImage() { + $gotImage = false; + //This returns an image to the browser + if (UserAccount::isLoggedIn()) { + $patron = UserAccount::getActiveUserObj(); + + //TODO: Take this out, the data should already be generated at this point + require_once ROOT_DIR . '/sys/YearInReview/YearInReviewGenerator.php'; + generateYearInReview($patron); + + if ($patron->hasYearInReview()) { + $slideNumber = $_REQUEST['slide'] ?? 1; + if (is_numeric($slideNumber)) { + $yearInReviewSettings = $patron->getYearInReviewSetting(); + $gotImage = $yearInReviewSettings->getSlideImage($patron, (int)$slideNumber); + } + } + } + if (!$gotImage) { + global $interface; + $interface->assign('module', 'Error'); + $interface->assign('action', 'Handle404'); + $module = 'Error'; + $action = 'Handle404'; + require_once ROOT_DIR . "/services/Error/Handle404.php"; + } + //Since this returns an image, don't return + die(); + } } diff --git a/code/web/sys/DBMaintenance/version_updates/24.12.00.php b/code/web/sys/DBMaintenance/version_updates/24.12.00.php index 7e6cf163e1..36dfbec238 100644 --- a/code/web/sys/DBMaintenance/version_updates/24.12.00.php +++ b/code/web/sys/DBMaintenance/version_updates/24.12.00.php @@ -97,7 +97,6 @@ function getUpdates24_12_00(): array { libraryId INT NOT NULL, UNIQUE (yearInReviewId, libraryId) ) ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_general_ci', - 'DROP TABLE user_year_in_review', 'CREATE TABLE user_year_in_review ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, userId INT NOT NULL, diff --git a/code/web/sys/YearInReview/YearInReviewSetting.php b/code/web/sys/YearInReview/YearInReviewSetting.php index 037556dc1b..17165e9051 100644 --- a/code/web/sys/YearInReview/YearInReviewSetting.php +++ b/code/web/sys/YearInReview/YearInReviewSetting.php @@ -211,7 +211,7 @@ public function getSlide(User $patron, int|string $slideNumber) : array { } $result['slideConfiguration'] = $slideInfo; - $result['modalBody'] = $this->formatSlide($slideInfo, $patron); + $result['modalBody'] = $this->formatSlide($slideInfo, $patron, $slideNumber, $this->year); $modalButtons = ''; if ($slideNumber > 1) { @@ -251,9 +251,113 @@ public function getSlide(User $patron, int|string $slideNumber) : array { return $result; } - private function formatSlide(stdClass $slideInfo, User $patron) : string { + private function formatSlide(stdClass $slideInfo, User $patron, int $slideNumber, string|int $year) : string { global $interface; + $interface->assign('slideNumber', $slideNumber); $interface->assign('slideInfo', $slideInfo); return $interface->fetch('YearInReview/slide.tpl'); } + + public function getSlideImage(User $patron, int|string $slideNumber) : bool { + //Load slide configuration for the year + $gotImage = true; + $configurationFile = ROOT_DIR . "/year_in_review/$this->year.json"; + if (file_exists($configurationFile)) { + $slideConfiguration = json_decode(file_get_contents($configurationFile)); + $userYearInResults = $patron->getYearInReviewResults(); + if ($userYearInResults !== false) { + if ($slideNumber > 0 && $slideNumber <= $userYearInResults->numSlidesToShow) { + $slideIndex = $userYearInResults->slidesToShow[$slideNumber - 1]; + $slideInfo = $slideConfiguration->slides[$slideIndex - 1]; + + foreach ($slideInfo->overlay_text as $overlayText) { + foreach ($userYearInResults->userData as $field => $value) { + $overlayText->text = str_replace("{" . $field . "}", $value, $overlayText->text); + } + } + + $gotImage = $this->createSlideImage($slideInfo); + } + } + } + + return $gotImage; + } + + private function createSlideImage(stdClass $slideInfo) : ?string { + $gotImage = false; + if (count($slideInfo->overlay_text) == 0) { + //This slide is not dynamic, we just return the static contents + }else{ + require_once ROOT_DIR . '/sys/Covers/CoverImageUtils.php'; + global $configArray; + //Get the background image for the slide + $backgroundImageFile = ROOT_DIR . '/year_in_review/images/' . $slideInfo->background; + $backgroundImageFile = realpath($backgroundImageFile); + $backgroundImage = imagecreatefrompng($backgroundImageFile); + $backgroundImageInfo = getimagesize($backgroundImageFile); + $backgroundWidth = $backgroundImageInfo[0]; + $backgroundHeight = $backgroundImageInfo[1]; + //Create a canvas for the slide + $slideCanvas = imagecreatetruecolor($backgroundWidth, $backgroundHeight); + //Display the background to the slide + imagecopy($slideCanvas, $backgroundImage, 0, 0, 0, 0, $backgroundWidth, $backgroundHeight); + + $font = ROOT_DIR . '/fonts/JosefinSans-Bold.ttf'; + $white = imagecolorallocate($slideCanvas, 255, 255, 255); + $black = imagecolorallocate($slideCanvas, 0, 0, 0); + + //Add overlay text to the image + foreach ($slideInfo->overlay_text as $overlayText) { + $overlayWidth = $overlayText->width; + if (str_ends_with($overlayWidth,'%')) { + $percent = str_replace('%', '', $overlayWidth) / 100; + $textWidth = $backgroundWidth * $percent; + }else{ + $textWidth = $overlayWidth; + } + $fontSize = $overlayText->font_size; + if (str_ends_with($fontSize,'em')) { + $fontSize = str_replace('em', '', $fontSize) * 16; + } + $left = $overlayText->left; + if (str_ends_with($left,'%')) { + $percent = str_replace('%', '', $left) / 100; + $left = $backgroundWidth * $percent; + }elseif (str_ends_with($left,'px')) { + $left = str_replace('px', '', $left); + } + $top = $overlayText->top; + if (str_ends_with($top,'%')) { + $percent = str_replace('%', '', $top) / 100; + $top = $backgroundWidth * $percent; + }elseif (str_ends_with($top,'px')) { + $top = str_replace('px', '', $top); + } + + if ($overlayText == 'white') { + $color = $white; + }else{ + $color = $black; + } + + [ + $totalHeight, + $lines, + ] = wrapTextForDisplay($font, $overlayText->text, $fontSize, $fontSize * .1, $textWidth); + if ($overlayText->align == 'center') { + addCenteredWrappedTextToImage($slideCanvas, $font, $lines, $fontSize, $fontSize * .1, $left, $top, $textWidth, $color); + }else{ + addWrappedTextToImage($slideCanvas, $font, $lines, $fontSize, $fontSize * .1, $left, $top, $color); + } + } + + //Output the image to the browser + imagepng($slideCanvas); + imagedestroy($slideCanvas); + $gotImage = true; + } + + return $gotImage; + } } \ No newline at end of file diff --git a/code/web/year_in_review/2024.json b/code/web/year_in_review/2024.json index 78a666b78e..0c1f46f837 100644 --- a/code/web/year_in_review/2024.json +++ b/code/web/year_in_review/2024.json @@ -2,28 +2,30 @@ "slides": [ { "title": "Your 2024 Shelved", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_1.png", + "background" : "2024_1.png", "overlay_text" : [] }, { "title": "Your Borrower Experience Points", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_2a.png", + "background" : "2024_2a.png", "overlay_text" : [ { - "text": "You checked out {totalCheckouts} titles in 2024.
And saved {yearlyCostSavings}.", - "left": "43%", - "top": "45%", + "text": "You checked out {totalCheckouts} titles in 2024.", + "left": "525px", + "top": "325px", "width": "40%", "height": "20%", - "color" : "#000000", + "color" : "black", + "font_size": "2em", + "align": "center" + }, + { + "text": "And saved {yearlyCostSavings}!", + "left": "525px", + "top": "450px", + "width": "40%", + "height": "20%", + "color" : "black", "font_size": "2em", "align": "center" } @@ -31,11 +33,7 @@ }, { "title": "Your Borrower Experience Points", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_2b.png", + "background" : "2024_2b.png", "overlay_text" : [ { "text": "You checked out {totalCheckouts} titles in 2024.", @@ -43,7 +41,7 @@ "top": "45%", "width": "40%", "height": "20%", - "color" : "#000000", + "color" : "black", "font_size": "2em", "align": "center" } @@ -51,31 +49,23 @@ }, { "title": "Your Hot Month", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_3a.png", + "background" : "2024_3a.png", "overlay_text" : [ { "text": "In {topMonth}, you borrowed the most from the library.", - "left": "20%", - "top": "30%", + "left": "225px", + "top": "200px", "width": "40%", "height": "20%", - "color" : "#FFFFFF", - "font_size": "2em", + "color" : "white", + "font_size": "2.5em", "align": "left" } ] }, { "title": "Every Month was Your Busy Month", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_3b.png", + "background" : "2024_3b.png", "overlay_text" : [ { "text": "You were a steady user of the library this year with a checkout average of {averageCheckouts} per month. What kept you coming back for more?", @@ -83,7 +73,7 @@ "top": "35%", "width": "40%", "height": "20%", - "color" : "#FFFFFF", + "color" : "white", "font_size": "2em", "align": "left" } @@ -91,86 +81,62 @@ }, { "title": "Top Formats", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_4.png", + "background" : "2024_4.png", "overlay_text" : [ { "text": "Your favorite ways to engage: {top_formats}", "position": "center", - "color" : "#000000" + "color" : "black" } ] }, { "title": "Favorite Genres", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_5.png", + "background" : "2024_5.png", "overlay_text" : [ { "text": "You dove into {top_genres} this year. Such impeccable taste!", "position": "center", - "color" : "#000000" + "color" : "black" } ] }, { "title": "Author You Couldn't Put Down", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_6.png", + "background" : "2024_6.png", "overlay_text" : [ { "text": "You checked out multiple titles by {top_author}.", "position": "center", - "color" : "#000000" + "color" : "black" } ] }, { "title": "Top Series", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_7.png", + "background" : "2024_7.png", "overlay_text" : [ { "text": "{top_series} kept you hooked in 2024!", "position": "center", - "color" : "#000000" + "color" : "black" } ] }, { "title": "Your 2025 Forecast", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_8.png", + "background" : "2024_8.png", "overlay_text" : [ { "text": "You might like these titles based on your 2024 picks: {recommendations}", "position": "center", - "color" : "#000000" + "color" : "black" } ] }, { "title": "Thank you for using the library!", - "background_tn" : "", - "background_xs" : "", - "background_sm" : "", - "background_md" : "", - "background_lg" : "2024_9.png", + "background" : "2024_9.png", "overlay_text" : [] } ]