PageRenderTime 20ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/badges/lib/bakerlib.php

https://github.com/mackensen/moodle
PHP | 177 lines | 66 code | 22 blank | 89 comment | 12 complexity | a69cf60933dc62c9afc0e607d6ee0be1 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Baking badges library.
  18. *
  19. * @package core
  20. * @subpackage badges
  21. * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. * @author Yuliya Bozhko <yuliya.bozhko@totaralms.com>
  24. */
  25. defined('MOODLE_INTERNAL') || die();
  26. /**
  27. * Information on PNG file chunks can be found at http://www.w3.org/TR/PNG/#11Chunks
  28. * Some other info on PNG that I used http://garethrees.org/2007/11/14/pngcrush/
  29. *
  30. * Example of use:
  31. * $png = new PNG_MetaDataHandler('file.png');
  32. *
  33. * if ($png->check_chunks("tEXt", "openbadge")) {
  34. * $newcontents = $png->add_chunks("tEXt", "openbadge", 'http://some.public.url/to.your.assertion.file');
  35. * }
  36. *
  37. * file_put_contents('file.png', $newcontents);
  38. */
  39. class PNG_MetaDataHandler
  40. {
  41. /** @var string File content as a string */
  42. private $_contents;
  43. /** @var int Length of the image file */
  44. private $_size;
  45. /** @var array Variable for storing parsed chunks */
  46. private $_chunks;
  47. /**
  48. * Prepares file for handling metadata.
  49. * Verifies that this file is a valid PNG file.
  50. * Unpacks file chunks and reads them into an array.
  51. *
  52. * @param string $contents File content as a string
  53. */
  54. public function __construct($contents) {
  55. $this->_contents = $contents;
  56. $png_signature = pack("C8", 137, 80, 78, 71, 13, 10, 26, 10);
  57. // Read 8 bytes of PNG header and verify.
  58. $header = substr($this->_contents, 0, 8);
  59. if ($header != $png_signature) {
  60. debugging('This is not a valid PNG image');
  61. }
  62. $this->_size = strlen($this->_contents);
  63. $this->_chunks = array();
  64. // Skip 8 bytes of IHDR image header.
  65. $position = 8;
  66. do {
  67. $chunk = @unpack('Nsize/a4type', substr($this->_contents, $position, 8));
  68. $this->_chunks[$chunk['type']][] = substr($this->_contents, $position + 8, $chunk['size']);
  69. // Skip 12 bytes chunk overhead.
  70. $position += $chunk['size'] + 12;
  71. } while ($position < $this->_size);
  72. }
  73. /**
  74. * Checks if a key already exists in the chunk of said type.
  75. * We need to avoid writing same keyword into file chunks.
  76. *
  77. * @param string $type Chunk type, like iTXt, tEXt, etc.
  78. * @param string $check Keyword that needs to be checked.
  79. *
  80. * @return boolean (true|false) True if file is safe to write this keyword, false otherwise.
  81. */
  82. public function check_chunks($type, $check) {
  83. if (array_key_exists($type, $this->_chunks)) {
  84. foreach (array_keys($this->_chunks[$type]) as $typekey) {
  85. list($key, $data) = explode("\0", $this->_chunks[$type][$typekey]);
  86. if (strcmp($key, $check) == 0) {
  87. debugging('Key "' . $check . '" already exists in "' . $type . '" chunk.');
  88. return false;
  89. }
  90. }
  91. }
  92. return true;
  93. }
  94. /**
  95. * Adds a chunk with keyword and data to the file content.
  96. * Chunk is added to the end of the file, before IEND image trailer.
  97. *
  98. * @param string $type Chunk type, like iTXt, tEXt, etc.
  99. * @param string $key Keyword that needs to be added.
  100. * @param string $value Currently an assertion URL that is added to an image metadata.
  101. *
  102. * @return string $result File content with a new chunk as a string. Can be used in file_put_contents() to write to a file.
  103. * @throws \moodle_exception when unsupported chunk type is defined.
  104. */
  105. public function add_chunks($type, $key, $value) {
  106. if (strlen($key) > 79) {
  107. debugging('Key is too big');
  108. }
  109. $dataparts = [];
  110. if ($type === 'iTXt') {
  111. // International textual data (iTXt).
  112. // Keyword: 1-79 bytes (character string).
  113. $dataparts[] = $key;
  114. // Null separator: 1 byte.
  115. $dataparts[] = "\x00";
  116. // Compression flag: 1 byte
  117. // A value of 0 means no compression.
  118. $dataparts[] = "\x00";
  119. // Compression method: 1 byte
  120. // If compression is disabled, the method should also be 0.
  121. $dataparts[] = "\x00";
  122. // Language tag: 0 or more bytes (character string)
  123. // When there is no language specified leave empty.
  124. // Null separator: 1 byte.
  125. $dataparts[] = "\x00";
  126. // Translated keyword: 0 or more bytes
  127. // When there is no translation specified, leave empty.
  128. // Null separator: 1 byte.
  129. $dataparts[] = "\x00";
  130. // Text: 0 or more bytes.
  131. $dataparts[] = $value;
  132. } else if ($type === 'tEXt') {
  133. // Textual data (tEXt).
  134. // Keyword: 1-79 bytes (character string).
  135. $dataparts[] = $key;
  136. // Null separator: 1 byte.
  137. $dataparts[] = "\0";
  138. // Text: n bytes (character string).
  139. $dataparts[] = $value;
  140. } else {
  141. throw new \moodle_exception('Unsupported chunk type: ' . $type);
  142. }
  143. $data = implode($dataparts);
  144. $crc = pack("N", crc32($type . $data));
  145. $len = pack("N", strlen($data));
  146. // Chunk format: length + type + data + CRC.
  147. // CRC is a CRC-32 computed over the chunk type and chunk data.
  148. $newchunk = $len . $type . $data . $crc;
  149. $this->_chunks[$type] = $data;
  150. $result = substr($this->_contents, 0, $this->_size - 12)
  151. . $newchunk
  152. . substr($this->_contents, $this->_size - 12, 12);
  153. return $result;
  154. }
  155. }