Issues (39)

src/Data/ECI.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * Class ECI
4
 *
5
 * @created      20.11.2020
6
 * @author       smiley <[email protected]>
7
 * @copyright    2020 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\QRCode\Data;
12
13
use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode};
14
use function mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, sprintf;
15
16
/**
17
 * Adds an ECI Designator
18
 *
19
 * ISO/IEC 18004:2000 8.4.1.1
20
 *
21
 * Please note that you have to take care for the correct data encoding when adding with QRCode::add*Segment()
22
 */
23
final class ECI extends QRDataModeAbstract{
24
25
	/**
26
	 * @inheritDoc
27
	 */
28
	public const DATAMODE = Mode::ECI;
29
30
	/**
31
	 * The current ECI encoding id
32
	 */
33
	private int $encoding;
34
35
	/**
36
	 * @inheritDoc
37
	 * @noinspection PhpMissingParentConstructorInspection
38
	 */
39
	public function __construct(int $encoding){
40
41
		if($encoding < 0 || $encoding > 999999){
42
			throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding));
43
		}
44
45
		$this->encoding = $encoding;
46
	}
47
48
	/**
49
	 * @inheritDoc
50
	 */
51
	public function getLengthInBits():int{
52
53
		if($this->encoding < 128){
54
			return 8;
55
		}
56
57
		if($this->encoding < 16384){
58
			return 16;
59
		}
60
61
		return 24;
62
	}
63
64
	/**
65
	 * Writes an ECI designator to the bitbuffer
66
	 *
67
	 * @inheritDoc
68
	 */
69
	public function write(BitBuffer $bitBuffer, int $versionNumber):QRDataModeInterface{
70
		$bitBuffer->put(self::DATAMODE, 4);
71
72
		if($this->encoding < 128){
73
			$bitBuffer->put($this->encoding, 8);
74
		}
75
		elseif($this->encoding < 16384){
76
			$bitBuffer->put(($this->encoding | 0x8000), 16);
77
		}
78
		elseif($this->encoding < 1000000){
79
			$bitBuffer->put(($this->encoding | 0xC00000), 24);
80
		}
81
82
		return $this;
83
	}
84
85
	/**
86
	 * Reads and parses the value of an ECI designator
87
	 *
88
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
89
	 */
90
	public static function parseValue(BitBuffer $bitBuffer):ECICharset{
91
		$firstByte = $bitBuffer->read(8);
92
93
		// just one byte
94
		if(($firstByte & 0b10000000) === 0){
95
			$id = ($firstByte & 0b01111111);
96
		}
97
		// two bytes
98
		elseif(($firstByte & 0b11000000) === 0b10000000){
99
			$id = ((($firstByte & 0b00111111) << 8) | $bitBuffer->read(8));
100
		}
101
		// three bytes
102
		elseif(($firstByte & 0b11100000) === 0b11000000){
103
			$id = ((($firstByte & 0b00011111) << 16) | $bitBuffer->read(16));
104
		}
105
		else{
106
			throw new QRCodeDataException(sprintf('error decoding ECI value first byte: %08b', $firstByte)); // @codeCoverageIgnore
107
		}
108
109
		return new ECICharset($id);
110
	}
111
112
	/**
113
	 * @codeCoverageIgnore Unused, but required as per interface
114
	 */
115
	public static function validateString(string $string):bool{
116
		return true;
117
	}
118
119
	/**
120
	 * Reads and decodes the ECI designator including the following byte sequence
121
	 *
122
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
123
	 */
124
	public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{
125
		$eciCharset = self::parseValue($bitBuffer);
126
		$nextMode   = $bitBuffer->read(4);
127
128
		if($nextMode !== Mode::BYTE){
129
			throw new QRCodeDataException(sprintf('ECI designator followed by invalid mode: "%04b"', $nextMode));
130
		}
131
132
		$data     = Byte::decodeSegment($bitBuffer, $versionNumber);
133
		$encoding = $eciCharset->getName();
134
135
		if($encoding === null){
136
			// The spec isn't clear on this mode; see
137
			// section 6.4.5: t does not say which encoding to assuming
138
			// upon decoding. I have seen ISO-8859-1 used as well as
139
			// Shift_JIS -- without anything like an ECI designator to
140
			// give a hint.
141
			$encoding = mb_detect_encoding($data, ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'], true);
142
143
			if($encoding === false){
144
				throw new QRCodeDataException('could not determine encoding in ECI mode'); // @codeCoverageIgnore
145
			}
146
		}
147
148
		return mb_convert_encoding($data, mb_internal_encoding(), $encoding);
0 ignored issues
show
It seems like mb_internal_encoding() can also be of type true; however, parameter $to_encoding of mb_convert_encoding() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
		return mb_convert_encoding($data, /** @scrutinizer ignore-type */ mb_internal_encoding(), $encoding);
Loading history...
149
	}
150
151
}
152