diff --git a/.upgradenotes/MDL-83886-2025022403115982.yml b/.upgradenotes/MDL-83886-2025022403115982.yml new file mode 100644 index 00000000000..62b64b5e031 --- /dev/null +++ b/.upgradenotes/MDL-83886-2025022403115982.yml @@ -0,0 +1,9 @@ +issueNumber: MDL-83886 +notes: + core_badges: + - message: >- + The class in badges/lib/bakerlib.php has been moved to + core_badges\png_metadata_handler. If you've extended or + directly used the old bakerlib.php, you'll need to update your code + to use the new namespaced class. + type: improved diff --git a/badges/lib/bakerlib.php b/badges/classes/png_metadata_handler.php old mode 100644 new mode 100755 similarity index 93% rename from badges/lib/bakerlib.php rename to badges/classes/png_metadata_handler.php index 23fbcc80186..eea1d8aa1a1 --- a/badges/lib/bakerlib.php +++ b/badges/classes/png_metadata_handler.php @@ -14,24 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Baking badges library. - * - * @package core - * @subpackage badges - * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @author Yuliya Bozhko - */ - -defined('MOODLE_INTERNAL') || die(); +namespace core_badges; /** * Information on PNG file chunks can be found at http://www.w3.org/TR/PNG/#11Chunks * Some other info on PNG that I used http://garethrees.org/2007/11/14/pngcrush/ * * Example of use: - * $png = new PNG_MetaDataHandler('file.png'); + * $png = new png_metadata_handler('file.png'); * * if ($png->check_chunks("tEXt", "openbadge")) { * $newcontents = $png->add_chunks("tEXt", "openbadge", 'http://some.public.url/to.your.assertion.file'); @@ -40,14 +30,22 @@ defined('MOODLE_INTERNAL') || die(); * file_put_contents('file.png', $newcontents); */ -class PNG_MetaDataHandler +/** + * Baking badges - PNG metadata handler. + * + * @package core_badges + * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Yuliya Bozhko + */ +class png_metadata_handler { /** @var string File content as a string */ - private $_contents; + private string $_contents; /** @var int Length of the image file */ - private $_size; + private int $_size; /** @var array Variable for storing parsed chunks */ - private $_chunks; + private array $_chunks; /** * Prepares file for handling metadata. @@ -56,7 +54,7 @@ class PNG_MetaDataHandler * * @param string $contents File content as a string */ - public function __construct($contents) { + public function __construct(string $contents) { $this->_contents = $contents; $png_signature = pack("C8", 137, 80, 78, 71, 13, 10, 26, 10); @@ -91,7 +89,7 @@ class PNG_MetaDataHandler * * @return boolean (true|false) True if file is safe to write this keyword, false otherwise. */ - public function check_chunks($type, $check) { + public function check_chunks(string $type, string $check): bool { if (array_key_exists($type, $this->_chunks)) { foreach (array_keys($this->_chunks[$type]) as $typekey) { list($key, $data) = explode("\0", $this->_chunks[$type][$typekey]); @@ -116,7 +114,7 @@ class PNG_MetaDataHandler * @return string $result File content with a new chunk as a string. Can be used in file_put_contents() to write to a file. * @throws \moodle_exception when unsupported chunk type is defined. */ - public function add_chunks($type, $key, $value) { + public function add_chunks(string $type, string $key, string $value): string { if (strlen($key) > 79) { debugging('Key is too big'); } diff --git a/badges/tests/fixtures/badge.jpg b/badges/tests/fixtures/badge.jpg new file mode 100644 index 00000000000..fa36613436a Binary files /dev/null and b/badges/tests/fixtures/badge.jpg differ diff --git a/badges/tests/png_metadata_handler_test.php b/badges/tests/png_metadata_handler_test.php new file mode 100644 index 00000000000..abe19e6c2c0 --- /dev/null +++ b/badges/tests/png_metadata_handler_test.php @@ -0,0 +1,156 @@ +. + +namespace core_badges; + +use core_badges\png_metadata_handler; + +/** + * Unit tests for PNG metadata handler + * + * @package core_badges + * @covers \core_badges\png_metadata_handler + * @copyright 2025 Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Dai Nguyen Trong + * @author Sara Arjona + */ +final class png_metadata_handler_test extends \advanced_testcase { + + /** + * Create a valid PNG file content for testing + * + * @return string The PNG file content + */ + protected function create_test_png(): string { + global $CFG; + + $badgepath = $CFG->dirroot . '/badges/tests/behat/badge.png'; + return file_get_contents($badgepath); + } + + /** + * Create a valid JPG file content for testing + * + * @return string The PNG file content + */ + protected function create_test_jpg(): string { + global $CFG; + + $badgepath = $CFG->dirroot . '/badges/tests/fixtures/badge.jpg'; + return file_get_contents($badgepath); + } + + /** + * Test PNG metadata handler constructor with valid PNG. + */ + public function test_constructor_valid_png(): void { + $this->resetAfterTest(); + + $content = $this->create_test_png(); + $handler = new png_metadata_handler($content); + $this->assertInstanceOf(png_metadata_handler::class, $handler); + } + + /** + * Test constructor with invalid PNG. + */ + public function test_constructor_invalid_png(): void { + $this->resetAfterTest(); + + $content = $this->create_test_jpg(); + $handler = new png_metadata_handler($content); + $this->assertDebuggingCalled('This is not a valid PNG image'); + $this->assertInstanceOf(png_metadata_handler::class, $handler); + } + + /** + * Test add_chunks method with valid chunks. + * + * @dataProvider add_chunks_provider + * @param string $type The chunk type + * @param string $key The key to add + * @param string|null $value The value to add + */ + public function test_add_chunks(string $type, string $key, ?string $value = null): void { + $this->resetAfterTest(); + + $content = $this->create_test_png(); + $handler = new png_metadata_handler($content); + $this->assertTrue($handler->check_chunks($type, 'openbadge')); + + $newcontent = $handler->add_chunks($type, $key, $value); + + // Create new handler with modified content to verify. + $newhandler = new png_metadata_handler($newcontent); + $this->assertFalse($newhandler->check_chunks($type, $key)); + $this->assertDebuggingCalled('Key "' . $key . '" already exists in "' . $type . '" chunk.'); + } + + /** + * Data provider for add_chunks test. + * + * @return array The data provider array + */ + public static function add_chunks_provider(): array { + return [ + 'tEXt' => [ + 'type' => 'tEXt', + 'key' => 'openbadge', + 'value' => 'http://example.com/badge', + ], + 'iTXt' => [ + 'type' => 'iTXt', + 'key' => 'openbadge', + 'value' => 'http://example.com/badge', + ], + ]; + } + + /** + * Test add_chunks method with invalid chunk type. + */ + public function test_add_chunks_invalid_type(): void { + $this->resetAfterTest(); + + $content = $this->create_test_png(); + $handler = new png_metadata_handler($content); + + $this->expectException(\moodle_exception::class); + $this->expectExceptionMessage('Unsupported chunk type: zTXt'); + + $handler->add_chunks('zTXt', 'openbadge', 'http://example.com/badge'); + } + + /** + * Test add_chunks method with too long key. + */ + public function test_add_chunks_long_key(): void { + $this->resetAfterTest(); + + $content = $this->create_test_png(); + $handler = new png_metadata_handler($content); + + $longkey = str_repeat('a', 80); + $this->assertTrue($handler->check_chunks('tEXt', $longkey)); + $newcontent = $handler->add_chunks('tEXt', $longkey, 'http://example.com/badge'); + $this->assertDebuggingCalled('Key is too big'); + + $newhandler = new png_metadata_handler($newcontent); + $this->assertFalse($newhandler->check_chunks('tEXt', $longkey)); + $this->assertDebuggingCalled('Key "' . $longkey . '" already exists in "tEXt" chunk.'); + } +} diff --git a/lib/badgeslib.php b/lib/badgeslib.php index 76137a0b28f..1347c52bb65 100644 --- a/lib/badgeslib.php +++ b/lib/badgeslib.php @@ -131,6 +131,8 @@ define('BACKPACK_MOVE_DOWN', 1); // Global badge class has been moved to the component namespace. class_alias('\core_badges\badge', 'badge'); +use core_badges\png_metadata_handler; + /** * Sends notifications to users about awarded badges. * @@ -707,7 +709,6 @@ function print_badge_image(badge $badge, stdClass $context, $size = 'small') { */ function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) { global $CFG, $USER; - require_once(__DIR__ . '/../badges/lib/bakerlib.php'); $badge = new badge($badgeid); $badge_context = $badge->get_context(); @@ -719,7 +720,7 @@ function badges_bake($hash, $badgeid, $userid = 0, $pathhash = false) { if ($file = $fs->get_file($badge_context->id, 'badges', 'badgeimage', $badge->id, '/', 'f3.png')) { $contents = $file->get_content(); - $filehandler = new PNG_MetaDataHandler($contents); + $filehandler = new png_metadata_handler($contents); // For now, the site backpack OB version will be used as default. $obversion = badges_open_badges_backpack_api(); $assertion = new core_badges_assertion($hash, $obversion); diff --git a/lib/db/renamedclasses.php b/lib/db/renamedclasses.php index 95f421ab53b..aaf471a6c89 100644 --- a/lib/db/renamedclasses.php +++ b/lib/db/renamedclasses.php @@ -40,4 +40,6 @@ $renamedclasses = [ 'core_reportbuilder\\report_access_exception' => 'core_reportbuilder\\exception\\report_access_exception', 'core_reportbuilder\\source_invalid_exception' => 'core_reportbuilder\\exception\\source_invalid_exception', 'core_reportbuilder\\source_unavailable_exception' => 'core_reportbuilder\\exception\\source_unavailable_exception', + // Since Moodle 5.0. + 'PNG_MetaDataHandler' => 'core_badges\\png_metadata_handler', ];