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