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: #<editor-fold defaultstate="collapsed" desc="protected">
20: /**
21: * @var array
22: */
23: protected $aContainer;
24:
25: /**
26: * @var string
27: */
28: protected $sPath;
29:
30: /**
31: * @var bool
32: */
33: protected $bIsLoaded;
34: #</editor-fold>
35:
36: /**
37: * @param string $sSettingsPath
38: *
39: * @return AbstractSettings
40: */
41: public function __construct($sSettingsPath)
42: {
43: $this->aContainer = [];
44: $this->sPath = $sSettingsPath;
45: $this->bIsLoaded = false;
46: }
47:
48: /**
49: *
50: * @param type $sName
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 type $sName
64: * @param type $mValue
65: */
66: public function __set($sName, $mValue)
67: {
68: $this->SetValue($sName, $mValue);
69: }
70:
71: /**
72: *
73: * @param mixed $sName
74: */
75: public function __get($sName)
76: {
77: return $this->GetValue($sName);
78: }
79:
80: /**
81: * @return array
82: */
83: public function GetValues()
84: {
85: return $this->aContainer;
86: }
87:
88: /**
89: *
90: * @return string
91: */
92: public function GetPath()
93: {
94: return $this->sPath;
95: }
96:
97: /**
98: * @param array $aValues
99: */
100: public function SetValues($aValues)
101: {
102: $this->aContainer = $aValues;
103: }
104:
105: /**
106: * @param string $sKey
107: *
108: * @return mixed
109: */
110: public function GetValue($sKey, $mDefault = null)
111: {
112: if (!$this->bIsLoaded) {
113: $this->Load();
114: }
115:
116: return (isset($this->aContainer[$sKey])) ? $this->aContainer[$sKey]->Value : $mDefault;
117: }
118:
119: /**
120: * @deprecated
121: *
122: * @param string $sKey
123: *
124: * @return mixed
125: */
126: public function GetConf($sKey, $mDefault = null)
127: {
128: return $this->GetValue($sKey, $mDefault);
129: }
130:
131: /**
132: * @param string $sKey
133: * @param mixed $mValue = null
134: *
135: * @return bool
136: */
137: public function SetValue($sKey, $mValue)
138: {
139: $bResult = false;
140:
141: $sType = (isset($this->aContainer[$sKey])) ? $this->aContainer[$sKey]->Type : \gettype($mValue);
142: if (!isset($this->aContainer[$sKey])) {
143: $this->aContainer[$sKey] = new SettingsProperty($sKey, $mValue, $sType);
144: }
145:
146: switch ($sType) {
147: case 'string':
148: $mValue = (string) $mValue;
149: break;
150: case 'int':
151: case 'integer':
152: $mValue = (int) $mValue;
153: break;
154: case 'bool':
155: case 'boolean':
156: $mValue = (bool) $mValue;
157: break;
158: case 'spec':
159: $mValue = $this->specValidate($mValue, $this->aContainer[$sKey]->SpecType);
160: break;
161: case 'array':
162: if (!Utils::IsAssocArray($mValue)) {
163: // rebuild array indexes
164: $mValue = array_values($mValue);
165: }
166: break;
167: default:
168: $mValue = null;
169: break;
170: }
171: $this->aContainer[$sKey]->Value = $mValue;
172: $this->aContainer[$sKey]->Changed = true;
173:
174: return $bResult;
175: }
176:
177: /**
178: * @deprecated
179: *
180: * @param string $sKey
181: * @param mixed $mValue = null
182: *
183: * @return bool
184: */
185: public function SetConf($sKey, $mValue)
186: {
187: return $this->SetValue($sKey, $mValue);
188: }
189:
190: public function IsExists()
191: {
192: return \file_exists($this->sPath);
193: }
194:
195: public function BackupConfigFile()
196: {
197: $sJsonFile = $this->sPath;
198: if (\file_exists($sJsonFile)) {
199: \copy($sJsonFile, $sJsonFile.'.bak');
200: }
201: }
202:
203: public function CheckConfigFile()
204: {
205: $bResult = true;
206:
207: // backup previous configuration
208: $sJsonFile = $this->sPath;
209: if (!\file_exists(\dirname($sJsonFile))) {
210: \set_error_handler(function () {
211: });
212: \mkdir(\dirname($sJsonFile), 0777);
213: \restore_error_handler();
214: $bResult = \file_exists(\dirname($sJsonFile));
215: }
216:
217: return $bResult;
218: }
219:
220: public function SaveDataToConfigFile($aData)
221: {
222: $sJsonFile = $this->sPath;
223: return (bool) \file_put_contents(
224: $sJsonFile,
225: \json_encode(
226: $aData,
227: JSON_PRETTY_PRINT | JSON_OBJECT_AS_ARRAY
228: )
229: );
230: }
231:
232: public function ParseData($aData)
233: {
234: $aContainer = [];
235:
236: if (\is_array($aData)) {
237: foreach ($aData as $sKey => $mValue) {
238: $sSpecType = null;
239: if (\is_array($mValue)) {
240: $sType = isset($mValue[1]) ? $mValue[1] : (isset($mValue[0]) ? \gettype($mValue[0]) : "string");
241: $sSpecType = isset($mValue[2]) ? $mValue[2] : null;
242: $mValue = isset($mValue[0]) ? $mValue[0] : "";
243: } else {
244: $sType = \gettype($mValue);
245: }
246:
247: switch ($sType) {
248: case 'string':
249: $mValue =(string) $mValue;
250: break;
251: case 'int':
252: case 'integer':
253: $sType = 'int';
254: $mValue = (int) $mValue;
255: break;
256: case 'bool':
257: case 'boolean':
258: $sType = 'bool';
259: $mValue = (bool) $mValue;
260: break;
261: case 'spec':
262: $mValue = $this->specConver($mValue, $sSpecType);
263: break;
264: case 'array':
265: break;
266: default:
267: $mValue = null;
268: break;
269: }
270: if (null !== $mValue) {
271: $aContainer[$sKey] = new SettingsProperty($sKey, $mValue, $sType, $sSpecType);
272: }
273: }
274: }
275:
276: return $aContainer;
277: }
278:
279: /**
280: * @param string $sJsonFile
281: *
282: * @return bool
283: */
284: public function LoadDataFromFile($sJsonFile)
285: {
286: $mResult = false;
287:
288: if (\file_exists($sJsonFile)) {
289: $sJsonData = \file_get_contents($sJsonFile);
290: $mResult = \json_decode($sJsonData, true);
291: }
292:
293: return $mResult;
294: }
295:
296: /**
297: * @param string $sJsonFile
298: *
299: * @return bool
300: */
301: public function Load($bForceLoad = false)
302: {
303: $bResult = false;
304: if ($this->bIsLoaded && !$bForceLoad) {
305: $bResult = true;
306: } else {
307: $mData = $this->LoadDataFromFile($this->sPath);
308:
309: if (!$mData) {
310: $mData = $this->LoadDataFromFile($this->sPath.'.bak');
311: if ($mData) {
312: \copy($this->sPath.'.bak', $this->sPath);
313: }
314: }
315:
316: if ($mData !== false) {
317: $this->aContainer = $this->ParseData($mData);
318: $this->bIsLoaded = true;
319: $bResult = true;
320: }
321: }
322: return $bResult;
323: }
324:
325: /**
326: * @return array
327: */
328: public function GetData()
329: {
330: $aResult = [];
331: foreach ($this->aContainer as $sKey => $mValue) {
332: $aValue = [];
333: if ($mValue->Type === 'spec') {
334: $mValue->Value = $this->specBackConver($mValue->Value, $mValue->SpecType);
335: $aValue[] = $mValue->SpecType;
336: }
337: \array_unshift(
338: $aValue,
339: $mValue->Value,
340: $mValue->Type
341: );
342:
343: $aResult[$sKey] = $aValue;
344: }
345:
346: return $aResult;
347: }
348:
349: /**
350: * @return bool
351: */
352: public function Save()
353: {
354: $bResult = false;
355: $aData = $this->GetData();
356: if (count($aData) > 0) {
357: if ($this->CheckConfigFile()) {
358: $this->BackupConfigFile();
359: if ($this->SaveDataToConfigFile($aData)) {
360: $bResult = true;
361: } else {
362: throw new \Aurora\System\Exceptions\SettingsException('Can\'t write settings to the configuration file');
363: }
364: }
365: }
366:
367: return $bResult;
368: }
369:
370: /**
371: * @param string $sValue
372: * @param string $sEnumName
373: *
374: * @return string
375: */
376: protected function specBackConver($sValue, $sEnumName)
377: {
378: $mResult = $sValue;
379: if (null !== $sEnumName) {
380: $mResult = Enums\EnumConvert::ToXml($sValue, $sEnumName);
381: }
382:
383: return $mResult;
384: }
385:
386: /**
387: * @param string $sValue
388: * @param string $sEnumName
389: *
390: * @return string
391: */
392: protected function specValidate($sValue, $sEnumName)
393: {
394: $mResult = null;
395: if (null !== $sEnumName) {
396: $mResult = Enums\EnumConvert::validate($sValue, $sEnumName);
397: }
398: return $mResult;
399: }
400:
401: /**
402: * @param string $sValue
403: * @param string $sEnumName
404: *
405: * @return string
406: */
407: protected function specConver($sValue, $sEnumName)
408: {
409: if (null !== $sEnumName) {
410: $mResult = Enums\EnumConvert::FromXml($sValue, $sEnumName);
411: }
412:
413: return $this->specValidate($mResult, $sEnumName);
414: }
415:
416: /**
417: * @return void
418: */
419: protected function init()
420: {
421: foreach ($this->aMap as $sKey => $aField) {
422: $this->aLowerMap[strtolower($sKey)] = $aField;
423: $this->SetValue($sKey, $aField[0]);
424: }
425: }
426: }
427: