Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.72% |
77 / 78 |
|
90.00% |
9 / 10 |
CRAP | |
0.00% |
0 / 1 |
Encoded | |
98.72% |
77 / 78 |
|
90.00% |
9 / 10 |
66 | |
0.00% |
0 / 1 |
setColumns | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
toArray | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
encodeValue | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
21 | |||
decodeValue | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
16 | |||
verify | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
encode | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
decode | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
isEncodedColumn | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
5 | |||
__set | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
__get | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Db\Record; |
15 | |
16 | /** |
17 | * Encoded record class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Db |
21 | * @author Nick Sagona, III <dev@nolainteractive.com> |
22 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
23 | * @license http://www.popphp.org/license New BSD License |
24 | * @version 6.5.0 |
25 | */ |
26 | class Encoded extends \Pop\Db\Record |
27 | { |
28 | |
29 | /** |
30 | * JSON-encoded fields |
31 | * @var array |
32 | */ |
33 | protected array $jsonFields = []; |
34 | |
35 | /** |
36 | * PHP-serialized fields |
37 | * @var array |
38 | */ |
39 | protected array $phpFields = []; |
40 | |
41 | /** |
42 | * Base64-encoded fields |
43 | * @var array |
44 | */ |
45 | protected array $base64Fields = []; |
46 | |
47 | /** |
48 | * Password-hashed fields |
49 | * @var array |
50 | */ |
51 | protected array $hashFields = []; |
52 | |
53 | /** |
54 | * Encrypted fields |
55 | * @var array |
56 | */ |
57 | protected array $encryptedFields = []; |
58 | |
59 | /** |
60 | * Hash algorithm |
61 | * @var string |
62 | */ |
63 | protected string $hashAlgorithm = PASSWORD_BCRYPT; |
64 | |
65 | /** |
66 | * Hash options |
67 | * @var array |
68 | */ |
69 | protected array $hashOptions = ['cost' => 10]; |
70 | |
71 | /** |
72 | * Cipher method |
73 | * @var ?string |
74 | */ |
75 | protected ?string $cipherMethod = null; |
76 | |
77 | /** |
78 | * Encrypted field key |
79 | * @var ?string |
80 | */ |
81 | protected ?string $key = null; |
82 | |
83 | /** |
84 | * Encrypted field IV (base64-encoded) |
85 | * @var ?string |
86 | */ |
87 | protected ?string $iv = null; |
88 | |
89 | /** |
90 | * Set all the table column values at once |
91 | * |
92 | * @param mixed $columns |
93 | * @throws Exception |
94 | * @return Encoded |
95 | */ |
96 | public function setColumns(mixed $columns = null): Encoded |
97 | { |
98 | if ($columns !== null) { |
99 | if (is_array($columns) || ($columns instanceof \ArrayObject)) { |
100 | $columns = $this->encode($columns); |
101 | } else if ($columns instanceof AbstractRecord) { |
102 | $columns = $this->encode($columns->toArray()); |
103 | } else { |
104 | throw new Exception('The parameter passed must be either an array, an array object or null.'); |
105 | } |
106 | |
107 | parent::setColumns($columns); |
108 | } |
109 | |
110 | return $this; |
111 | } |
112 | |
113 | /** |
114 | * Get column values as array |
115 | * |
116 | * @throws Exception |
117 | * @return array |
118 | */ |
119 | public function toArray(): array |
120 | { |
121 | $result = parent::toArray(); |
122 | |
123 | foreach ($result as $key => $value) { |
124 | if (($this->isEncodedColumn($key)) && ($value !== null)) { |
125 | $result[$key] = $this->decodeValue($key, $value); |
126 | } |
127 | } |
128 | |
129 | return $result; |
130 | } |
131 | |
132 | /** |
133 | * Encode value |
134 | * |
135 | * @param string $key |
136 | * @param mixed $value |
137 | * @throws Exception |
138 | * @return string |
139 | */ |
140 | public function encodeValue(string $key, mixed $value): string |
141 | { |
142 | if (in_array($key, $this->jsonFields)) { |
143 | if (!((is_string($value) && (json_decode($value) !== false)) && (json_last_error() == JSON_ERROR_NONE))) { |
144 | $value = json_encode($value); |
145 | } |
146 | } else if (in_array($key, $this->phpFields)) { |
147 | if (!(is_string($value) && (@unserialize($value) !== false))) { |
148 | $value = serialize($value); |
149 | } |
150 | } else if (in_array($key, $this->base64Fields)) { |
151 | if (!(is_string($value) && (base64_encode(base64_decode($value)) === $value))) { |
152 | $value = base64_encode($value); |
153 | } |
154 | } else if (in_array($key, $this->hashFields)) { |
155 | $info = password_get_info($value); |
156 | if (((int)$info['algo'] == 0) || (strtolower($info['algoName']) == 'unknown')) { |
157 | $value = password_hash($value, $this->hashAlgorithm, $this->hashOptions); |
158 | } |
159 | } else if (in_array($key, $this->encryptedFields)) { |
160 | if (empty($this->cipherMethod) || empty($this->key) || empty($this->iv)) { |
161 | throw new Exception('Error: The encryption properties have not been set for this class.'); |
162 | } |
163 | $decodedValue = $this->decodeValue($key, $value); |
164 | if (!(is_string($value) && ($decodedValue !== false) && ($decodedValue != $value))) { |
165 | $value = base64_encode( |
166 | openssl_encrypt($value, $this->cipherMethod, $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv)) |
167 | ); |
168 | } |
169 | } |
170 | |
171 | return $value; |
172 | } |
173 | |
174 | /** |
175 | * Decode value |
176 | * |
177 | * @param string $key |
178 | * @param string $value |
179 | * @throws Exception |
180 | * @return mixed |
181 | */ |
182 | public function decodeValue(string $key, string $value): mixed |
183 | { |
184 | if (in_array($key, $this->jsonFields)) { |
185 | if ($value !== null) { |
186 | $jsonValue = @json_decode($value, true); |
187 | if (json_last_error() === JSON_ERROR_NONE) { |
188 | $value = $jsonValue; |
189 | } |
190 | } |
191 | } else if (in_array($key, $this->phpFields)) { |
192 | if ($value !== null) { |
193 | $phpValue = @unserialize($value); |
194 | if ($phpValue !== false) { |
195 | $value = $phpValue; |
196 | } |
197 | } |
198 | } else if (in_array($key, $this->base64Fields)) { |
199 | if ($value !== null) { |
200 | $base64Value = @base64_decode($value, true); |
201 | if ($base64Value !== false) { |
202 | $value = $base64Value; |
203 | } |
204 | } |
205 | } else if (in_array($key, $this->encryptedFields)) { |
206 | if (empty($this->cipherMethod) || empty($this->key) || empty($this->iv)) { |
207 | throw new Exception('Error: The encryption properties have not been set for this class.'); |
208 | } |
209 | if ($value !== null) { |
210 | $base64Value = @base64_decode($value, true); |
211 | if ($base64Value !== false) { |
212 | $value = openssl_decrypt( |
213 | base64_decode($value), $this->cipherMethod, $this->key, OPENSSL_RAW_DATA, base64_decode($this->iv) |
214 | ); |
215 | } |
216 | } |
217 | } |
218 | |
219 | return $value; |
220 | } |
221 | |
222 | /** |
223 | * Verify value against hash |
224 | * |
225 | * @param string $key |
226 | * @param string $value |
227 | * @return bool |
228 | */ |
229 | public function verify(string $key, string $value): bool |
230 | { |
231 | return password_verify($value, $this->{$key}); |
232 | } |
233 | |
234 | /** |
235 | * Scrub the column values and encode them |
236 | * |
237 | * @param array $columns |
238 | * @throws Exception |
239 | * @return array |
240 | */ |
241 | public function encode(array $columns): array |
242 | { |
243 | foreach ($columns as $key => $value) { |
244 | if (($value !== null) && ($this->isEncodedColumn($key))) { |
245 | $columns[$key] = $this->encodeValue($key, $value); |
246 | } |
247 | } |
248 | |
249 | return $columns; |
250 | } |
251 | |
252 | /** |
253 | * Scrub the column values and decode them |
254 | * |
255 | * @param array $columns |
256 | * @throws Exception |
257 | * @return array |
258 | */ |
259 | public function decode(array $columns): array |
260 | { |
261 | foreach ($columns as $key => $value) { |
262 | if (($this->isEncodedColumn($key)) && ($value !== null)) { |
263 | $columns[$key] = $this->decodeValue($key, $value); |
264 | } |
265 | } |
266 | |
267 | return $columns; |
268 | } |
269 | |
270 | /** |
271 | * Determine if column is an encoded column |
272 | * |
273 | * @param string $key |
274 | * @return bool |
275 | */ |
276 | public function isEncodedColumn(string $key): bool |
277 | { |
278 | return (in_array($key, $this->jsonFields) || in_array($key, $this->phpFields) || |
279 | in_array($key, $this->base64Fields) || in_array($key, $this->hashFields) || in_array($key, $this->encryptedFields)); |
280 | } |
281 | |
282 | /** |
283 | * Magic method to set the property to the value of $this->rowGateway[$name] |
284 | * |
285 | * @param string $name |
286 | * @param mixed $value |
287 | * @throws Exception |
288 | * @return void |
289 | */ |
290 | public function __set(string $name, mixed $value): void |
291 | { |
292 | if (($value !== null) && ($this->isEncodedColumn($name))) { |
293 | $value = $this->encodeValue($name, $value); |
294 | } |
295 | parent::__set($name, $value); |
296 | } |
297 | |
298 | /** |
299 | * Magic method to return the value of $this->rowGateway[$name] |
300 | * |
301 | * @param string $name |
302 | * @throws Exception |
303 | * @return mixed |
304 | */ |
305 | public function __get(string $name): mixed |
306 | { |
307 | $value = parent::__get($name); |
308 | |
309 | if (($this->isEncodedColumn($name)) && ($value !== null)) { |
310 | $value = $this->decodeValue($name, $value); |
311 | } |
312 | |
313 | return $value; |
314 | } |
315 | |
316 | } |