|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace lordelph\SIP2; |
|
4
|
|
|
|
|
5
|
|
|
use lordelph\SIP2\Exception\LogicException; |
|
6
|
|
|
|
|
7
|
|
|
/** |
|
8
|
|
|
* SIP2Message Class |
|
9
|
|
|
* |
|
10
|
|
|
* This class provides support for getting/setting request and response variables with some magic methods |
|
11
|
|
|
* as well as SIP2 CRC calculation |
|
12
|
|
|
* |
|
13
|
|
|
* @licence https://opensource.org/licenses/MIT |
|
14
|
|
|
* @copyright Paul Dixon <[email protected]> |
|
15
|
|
|
*/ |
|
16
|
|
|
abstract class SIP2Message |
|
17
|
|
|
{ |
|
18
|
|
|
/** |
|
19
|
|
|
* @var array provides a list of the variables this message can use. Array key is the variable name in |
|
20
|
|
|
* StudlyCaps, value is an array which can contain type, default values |
|
21
|
|
|
*/ |
|
22
|
|
|
protected $var=[]; |
|
23
|
|
|
|
|
24
|
|
|
/** @var integer|null current timestamp, useful for testing */ |
|
25
|
|
|
protected $timestamp = null; |
|
26
|
|
|
|
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* Calculate SIP2 CRC value |
|
30
|
|
|
* @param string $buf |
|
31
|
|
|
* @return string |
|
32
|
|
|
*/ |
|
33
|
38 |
|
protected static function crc(string $buf) : string |
|
34
|
|
|
{ |
|
35
|
38 |
|
$sum = 0; |
|
36
|
|
|
|
|
37
|
38 |
|
$len = strlen($buf); |
|
38
|
38 |
|
for ($n = 0; $n < $len; $n++) { |
|
39
|
38 |
|
$sum = $sum + ord(substr($buf, $n, 1)); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
38 |
|
$crc = ($sum & 0xFFFF) * -1; |
|
43
|
|
|
|
|
44
|
|
|
/* 2008.03.15 - Fixed a bug that allowed the checksum to be larger then 4 digits */ |
|
45
|
38 |
|
return substr(sprintf("%4X", $crc), -4, 4); |
|
46
|
|
|
} |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Check if class supports given variable |
|
50
|
|
|
* @param string $name |
|
51
|
|
|
* @return bool |
|
52
|
|
|
*/ |
|
53
|
19 |
|
public function hasVariable(string $name) : bool |
|
54
|
|
|
{ |
|
55
|
19 |
|
return isset($this->var[$name]); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Set default value for a variable - this can be overridden by setVariable |
|
60
|
|
|
* |
|
61
|
|
|
* This method will allow you to attempt to set a default for a variable which the derived class |
|
62
|
|
|
* does not support, in which case it is silently ignored. |
|
63
|
|
|
* |
|
64
|
|
|
* @param string $name |
|
65
|
|
|
* @param string|array $value |
|
66
|
|
|
*/ |
|
67
|
3 |
|
public function setDefault(string $name, $value) |
|
68
|
|
|
{ |
|
69
|
3 |
|
if ($this->hasVariable($name)) { |
|
70
|
|
|
$this->var[$name]['default'] = $value; |
|
71
|
|
|
} |
|
72
|
3 |
|
} |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* Get value of specific variable. |
|
76
|
|
|
* |
|
77
|
|
|
* There is also a magic method which instead of getVariable('PatronStatus') would instead allow |
|
78
|
|
|
* getPatronStatus() to be called |
|
79
|
|
|
* |
|
80
|
|
|
* @param string $varName |
|
81
|
|
|
* @return string|array |
|
82
|
|
|
*/ |
|
83
|
37 |
|
public function getVariable(string $varName) |
|
84
|
|
|
{ |
|
85
|
37 |
|
$this->ensureVariableExists($varName); |
|
86
|
36 |
|
return $this->var[$varName]['value'] ?? |
|
87
|
18 |
|
$this->var[$varName]['default'] ?? |
|
88
|
36 |
|
$this->handleMissing($varName); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Get name/values of all variables |
|
93
|
|
|
* |
|
94
|
|
|
* This can be useful for building JSON-based results of SIP2 responses |
|
95
|
|
|
* @return array |
|
96
|
|
|
*/ |
|
97
|
1 |
|
public function getAll() |
|
98
|
|
|
{ |
|
99
|
1 |
|
$result=[]; |
|
100
|
1 |
|
foreach ($this->var as $name => $data) { |
|
101
|
1 |
|
$result[$name] = $this->getVariable($name); |
|
102
|
|
|
} |
|
103
|
1 |
|
return $result; |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* Set variable |
|
108
|
|
|
* |
|
109
|
|
|
* Variables which are defined as timestamps are converted to SIP2 date format automatically |
|
110
|
|
|
* |
|
111
|
|
|
* @param $varName |
|
112
|
|
|
* @param string|array $value |
|
113
|
|
|
*/ |
|
114
|
35 |
|
public function setVariable($varName, $value) |
|
115
|
|
|
{ |
|
116
|
35 |
|
$this->ensureVariableExists($varName); |
|
117
|
|
|
|
|
118
|
|
|
//check type... |
|
119
|
35 |
|
$type = $this->var[$varName]['type'] ?? 'string'; |
|
120
|
|
|
switch ($type) { |
|
121
|
35 |
|
case 'timestamp': |
|
122
|
1 |
|
$value = $this->datestamp($value); |
|
123
|
1 |
|
break; |
|
124
|
35 |
|
case 'array': |
|
125
|
15 |
|
$value = is_array($value) ? $value : [$value]; |
|
126
|
15 |
|
break; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
35 |
|
$this->var[$varName]['value'] = $value; |
|
130
|
35 |
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* If $varName is defined as an array, this will append given value. Otherwise value is set as normal |
|
134
|
|
|
* @param string $varName |
|
135
|
|
|
* @param string $value |
|
136
|
|
|
*/ |
|
137
|
16 |
|
public function addVariable(string $varName, $value) |
|
138
|
|
|
{ |
|
139
|
16 |
|
$this->ensureVariableExists($varName); |
|
140
|
16 |
|
$type = $this->var[$varName]['type'] ?? 'string'; |
|
141
|
16 |
|
if (($type === 'array') && isset($this->var[$varName]['value'])) { |
|
142
|
4 |
|
$this->var[$varName]['value'][] = $value; |
|
143
|
|
|
} else { |
|
144
|
16 |
|
$this->setVariable($varName, $value); |
|
145
|
|
|
} |
|
146
|
16 |
|
} |
|
147
|
|
|
|
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Get current timestamp, which can be override with setTimestamp for testing |
|
151
|
|
|
* @return int |
|
152
|
|
|
*/ |
|
153
|
12 |
|
public function getTimestamp() |
|
154
|
|
|
{ |
|
155
|
12 |
|
return $this->timestamp ?? time(); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* Sets current timestamp, which is useful for creating predictable tests |
|
160
|
|
|
* @param $timestamp |
|
161
|
|
|
*/ |
|
162
|
14 |
|
public function setTimestamp($timestamp) |
|
163
|
|
|
{ |
|
164
|
14 |
|
$this->timestamp = $timestamp; |
|
165
|
14 |
|
} |
|
166
|
|
|
|
|
167
|
12 |
|
protected function datestamp($timestamp = '') |
|
168
|
|
|
{ |
|
169
|
|
|
/* generate a SIP2 compatible datestamp */ |
|
170
|
|
|
/* From the spec: |
|
171
|
|
|
* YYYYMMDDZZZZHHMMSS. |
|
172
|
|
|
* All dates and times are expressed according to the ANSI standard X3.30 for date and X3.43 for time. |
|
173
|
|
|
* The ZZZZ field should contain blanks (code $20) to represent local time. To represent universal time, |
|
174
|
|
|
* a Z character(code $5A) should be put in the last (right hand) position of the ZZZZ field. |
|
175
|
|
|
* To represent other time zones the appropriate character should be used; a Q character (code $51) |
|
176
|
|
|
* should be put in the last (right hand) position of the ZZZZ field to represent Atlantic Standard Time. |
|
177
|
|
|
* When possible local time is the preferred format. |
|
178
|
|
|
*/ |
|
179
|
12 |
|
if ($timestamp != '') { |
|
180
|
|
|
/* Generate a proper date time from the date provided */ |
|
181
|
1 |
|
return date('Ymd His', $timestamp); |
|
182
|
|
|
} else { |
|
183
|
|
|
/* Current Date/Time */ |
|
184
|
12 |
|
return date('Ymd His', $this->getTimestamp()); |
|
185
|
|
|
} |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
37 |
|
protected function ensureVariableExists($name) |
|
189
|
|
|
{ |
|
190
|
37 |
|
if (!isset($this->var[$name])) { |
|
191
|
1 |
|
throw new LogicException(get_class($this) . ' has no ' . $name . ' member'); |
|
192
|
|
|
} |
|
193
|
37 |
|
} |
|
194
|
|
|
|
|
195
|
1 |
|
protected function handleMissing($varName) |
|
196
|
|
|
{ |
|
197
|
1 |
|
throw new LogicException(get_class($this) . '::set' . $varName . ' must be called'); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
34 |
|
public function __call($name, $arguments) |
|
201
|
|
|
{ |
|
202
|
34 |
|
if (!preg_match('/^(get|set)(.+)$/', $name, $match)) { |
|
203
|
1 |
|
throw new LogicException(get_class($this) . ' has no ' . $name . ' method'); |
|
204
|
|
|
} |
|
205
|
33 |
|
$varName = $match[2]; |
|
206
|
|
|
|
|
207
|
|
|
//get? |
|
208
|
33 |
|
if ($match[1] === 'get') { |
|
209
|
17 |
|
return $this->getVariable($varName); |
|
210
|
|
|
} |
|
211
|
|
|
//set |
|
212
|
18 |
|
$this->setVariable($varName, $arguments[0]); |
|
213
|
18 |
|
return $this; |
|
214
|
|
|
} |
|
215
|
|
|
} |
|
216
|
|
|
|