1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\System;
9:
10: /**
11: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
12: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
13: * @copyright Copyright (c) 2019, Afterlogic Corp.
14: */
15: abstract class AbstractSettings
16: {
17: public const JSON_FILE_NAME = 'config.json';
18:
19: /**
20: * @var array
21: */
22: protected $aContainer;
23:
24: /**
25: * @var string
26: */
27: protected $sPath;
28:
29: /**
30: * @var bool
31: */
32: protected $bIsLoaded;
33:
34: /**
35: * @param string $sSettingsPath
36: *
37: * @return AbstractSettings
38: */
39: public function __construct($sSettingsPath)
40: {
41: $this->aContainer = [];
42: $this->sPath = $sSettingsPath;
43: $this->bIsLoaded = false;
44: }
45:
46: /**
47: *
48: * @param string $sName
49: *
50: * @return bool
51: */
52: public function __isset($sName)
53: {
54: if (!$this->bIsLoaded) {
55: $this->Load();
56: }
57:
58: return isset($this->aContainer[$sName]);
59: }
60:
61: /**
62: *
63: * @param string $sName
64: * @param mixed $mValue
65: */
66: public function __set($sName, $mValue)
67: {
68: $this->SetValue($sName, $mValue);
69: }
70:
71: /**
72: *
73: * @param string $sName
74: *
75: * @return mixed
76: */
77: public function __get($sName)
78: {
79: return $this->GetValue($sName);
80: }
81:
82: /**
83: * @return array
84: */
85: public function GetValues()
86: {
87: return $this->aContainer;
88: }
89:
90: /**
91: *
92: * @return string
93: */
94: public function GetPath()
95: {
96: return $this->sPath;
97: }
98:
99: /**
100: * @param array $aValues
101: */
102: public function SetValues($aValues)
103: {
104: $this->aContainer = $aValues;
105: }
106:
107: /**
108: * @param string $sKey
109: *
110: * @return mixed
111: */
112: public function GetValue($sKey, $mDefault = null)
113: {
114: if (!$this->bIsLoaded) {
115: $this->Load();
116: }
117:
118: $value = $mDefault;
119: if (isset($this->aContainer[$sKey])) {
120: $prop = $this->aContainer[$sKey];
121: if ($prop->Type === 'encrypted') {
122: $value = \Aurora\System\Utils::DecryptValue($prop->Value);
123: if (!$value) {
124: $value = $prop->Value;
125: $this->SetValue($sKey, $value);
126: $this->Save();
127: }
128: } else {
129: $value = $prop->Value;
130: }
131: }
132:
133: return $value;
134: }
135:
136: /**
137: * @param string $sKey
138: *
139: * @return SettingsProperty
140: */
141: public function GetSettingsProperty($sKey)
142: {
143: if (!$this->bIsLoaded) {
144: $this->Load();
145: }
146:
147: return (isset($this->aContainer[$sKey])) ? $this->aContainer[$sKey] : null;
148: }
149:
150:
151: /**
152: * @deprecated
153: *
154: * @param string $sKey
155: *
156: * @return mixed
157: */
158: public function GetConf($sKey, $mDefault = null)
159: {
160: return $this->GetValue($sKey, $mDefault);
161: }
162:
163: /**
164: * @param string $sKey
165: * @param mixed $mValue = null
166: *
167: * @return bool
168: */
169: public function SetValue($sKey, $mValue)
170: {
171: $bResult = false;
172:
173: $sType = (isset($this->aContainer[$sKey])) ? $this->aContainer[$sKey]->Type : \gettype($mValue);
174: if (!isset($this->aContainer[$sKey])) {
175: $this->aContainer[$sKey] = new SettingsProperty($mValue, $sType);
176: }
177:
178: switch ($sType) {
179: case 'string':
180: $mValue = (string) $mValue;
181: break;
182: case 'int':
183: case 'integer':
184: $mValue = (int) $mValue;
185: break;
186: case 'bool':
187: case 'boolean':
188: $mValue = (bool) $mValue;
189: break;
190: case 'spec':
191: $mValue = $this->specValidate($mValue, $this->aContainer[$sKey]->SpecType);
192: break;
193: case 'array':
194: if (!Utils::IsAssocArray($mValue)) {
195: // rebuild array indexes
196: $mValue = array_values($mValue);
197: }
198: break;
199: case 'encrypted':
200: $mValue = \Aurora\System\Utils::EncryptValue($mValue);
201: break;
202: default:
203: $mValue = null;
204: break;
205: }
206: $this->aContainer[$sKey]->Value = $mValue;
207: $this->aContainer[$sKey]->Changed = true;
208:
209: return $bResult;
210: }
211:
212: /**
213: * @deprecated
214: *
215: * @param string $sKey
216: * @param mixed $mValue = null
217: *
218: * @return bool
219: */
220: public function SetConf($sKey, $mValue)
221: {
222: return $this->SetValue($sKey, $mValue);
223: }
224:
225: public function IsExists()
226: {
227: return \file_exists($this->sPath);
228: }
229:
230: public function BackupConfigFile()
231: {
232: $sJsonFile = $this->sPath;
233: if (\file_exists($sJsonFile)) {
234: \copy($sJsonFile, $sJsonFile . '.bak');
235: }
236: }
237:
238: public function LoadDataFromBackup()
239: {
240: return $this->LoadDataFromFile($this->sPath . '.bak');
241: }
242:
243: public function CheckConfigFile()
244: {
245: $bResult = true;
246:
247: // backup previous configuration
248: $sJsonFile = $this->sPath;
249: if (!\file_exists(\dirname($sJsonFile))) {
250: \set_error_handler(function () {});
251: \mkdir(\dirname($sJsonFile), 0777);
252: \restore_error_handler();
253: $bResult = \file_exists(\dirname($sJsonFile));
254: }
255:
256: return $bResult;
257: }
258:
259: public function SaveDataToConfigFile($aData)
260: {
261: $sJsonFile = $this->sPath;
262: return (bool) \file_put_contents(
263: $sJsonFile,
264: \json_encode(
265: $aData,
266: JSON_PRETTY_PRINT | JSON_OBJECT_AS_ARRAY
267: )
268: );
269: }
270:
271: public function ParseData($aData)
272: {
273: $aContainer = [];
274:
275: if (\is_array($aData)) {
276: foreach ($aData as $sKey => $mValue) {
277: if (isset($aData[$sKey])) {
278: $sSpecType = null;
279: $sDescription = '';
280: if (\is_array($mValue)) {
281: $sType = isset($mValue[1]) ? $mValue[1] : (isset($mValue[0]) ? \gettype($mValue[0]) : "string");
282: $sSpecType = isset($mValue[2]) ? $mValue[2] : null;
283: $sDescription = isset($mValue[3]) ? $mValue[3] : '';
284: $mValue = isset($mValue[0]) ? $mValue[0] : '';
285: if (isset($aData[$sKey . '_Description'])) {
286: $sDescription = isset($aData[$sKey . '_Description'][0]) ? $aData[$sKey . '_Description'][0] : '';
287: unset($aData[$sKey . '_Description']);
288: }
289: } else {
290: $sType = \gettype($mValue);
291: }
292:
293: switch ($sType) {
294: case 'string':
295: $mValue = (string) $mValue;
296: break;
297: case 'int':
298: case 'integer':
299: $sType = 'int';
300: $mValue = (int) $mValue;
301: break;
302: case 'bool':
303: case 'boolean':
304: $sType = 'bool';
305: $mValue = (bool) $mValue;
306: break;
307: case 'spec':
308: $mValue = $this->specConver($mValue, $sSpecType);
309: break;
310: case 'array':
311: break;
312: default:
313: $mValue = null;
314: break;
315: }
316: if (null !== $mValue) {
317: $aContainer[$sKey] = new SettingsProperty($mValue, $sType, $sSpecType, $sDescription);
318: }
319: }
320: }
321: }
322:
323: return $aContainer;
324: }
325:
326: /**
327: * @param string $sJsonFile
328: *
329: * @return bool
330: */
331: public function LoadDataFromFile($sJsonFile)
332: {
333: $mResult = false;
334:
335: if (\file_exists($sJsonFile)) {
336: $sJsonData = \file_get_contents($sJsonFile);
337: $mResult = \json_decode($sJsonData, true);
338: }
339:
340: return $mResult;
341: }
342:
343: /**
344: * @param bool $bForceLoad
345: *
346: * @return bool
347: */
348: public function Load($bForceLoad = false)
349: {
350: $bResult = false;
351: if ($this->bIsLoaded && !$bForceLoad) {
352: $bResult = true;
353: } else {
354: $mData = false;
355:
356: if (\file_exists($this->sPath)) {
357: $mData = $this->LoadDataFromFile($this->sPath);
358: }
359:
360: if (!$mData) {
361: $mData = $this->LoadDataFromBackup();
362: }
363:
364: if ($mData) {
365: $aParsedData = $this->ParseData($mData);
366: foreach ($aParsedData as $key => $val) {
367: $val->IsDefault = false;
368: if (isset($this->aContainer[$key])) {
369: $val->Description = $this->aContainer[$key]->Description;
370: }
371: $this->aContainer[$key] = $val;
372: }
373: $bResult = true;
374: } else {
375: $bResult = $this->Save();
376: }
377:
378: $this->bIsLoaded = true;
379: }
380:
381: return $bResult;
382: }
383:
384: /**
385: * @return array
386: */
387: public function GetData()
388: {
389: $aResult = [];
390: foreach ($this->aContainer as $sKey => $mValue) {
391: $aValue = [];
392: $value = $mValue->Value;
393: if ($mValue->Type === 'spec') {
394: $value = $this->specBackConver($mValue->Value, $mValue->SpecType);
395: $aValue[] = $mValue->SpecType;
396: } else {
397: $aValue[] = null;
398: }
399: \array_unshift(
400: $aValue,
401: $value,
402: $mValue->Type
403: );
404: $aValue[] = $mValue->Description;
405:
406: $aResult[$sKey] = $aValue;
407: }
408:
409: return $aResult;
410: }
411:
412: /**
413: * @param bool $bBackupConfigFile
414: *
415: * @return bool
416: */
417: public function Save($bBackupConfigFile = true)
418: {
419: $bResult = false;
420: $aData = $this->GetData();
421: if (count($aData) > 0) {
422: if ($this->CheckConfigFile()) {
423: if ($bBackupConfigFile) {
424: $this->BackupConfigFile();
425: }
426: if ($this->SaveDataToConfigFile($aData)) {
427: $bResult = true;
428: } else {
429: throw new \Aurora\System\Exceptions\SettingsException('Can\'t write settings to the configuration file');
430: }
431: }
432: }
433:
434: return $bResult;
435: }
436:
437: /**
438: * @param string $sValue
439: * @param string $sEnumName
440: *
441: * @return string
442: */
443: protected function specBackConver($sValue, $sEnumName)
444: {
445: $mResult = $sValue;
446: if (null !== $sEnumName) {
447: $mResult = Enums\EnumConvert::ToXml($sValue, $sEnumName);
448: }
449:
450: return $mResult;
451: }
452:
453: /**
454: * @param string $sValue
455: * @param string $sEnumName
456: *
457: * @return string
458: */
459: protected function specValidate($sValue, $sEnumName)
460: {
461: $mResult = null;
462: if (null !== $sEnumName) {
463: $mResult = Enums\EnumConvert::validate($sValue, $sEnumName);
464: }
465: return $mResult;
466: }
467:
468: /**
469: * @param string $sValue
470: * @param string $sEnumName
471: *
472: * @return string
473: */
474: protected function specConver($sValue, $sEnumName)
475: {
476: $mResult = null;
477: if (null !== $sEnumName) {
478: $mResult = Enums\EnumConvert::FromXml($sValue, $sEnumName);
479: }
480:
481: return $this->specValidate($mResult, $sEnumName);
482: }
483:
484: /**
485: * @return void
486: */
487: protected function init() {}
488: }
489: