PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/pocketmine/utils/Config.php

https://gitlab.com/matthww/Elywing
PHP | 462 lines | 286 code | 45 blank | 131 comment | 24 complexity | d492e125041c6914692e8065aa925ff4 MD5 | raw file
  1. <?php
  2. /*
  3. *
  4. * ____ _ _ __ __ _ __ __ ____
  5. * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
  6. * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
  7. * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
  8. * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Lesser General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * @author PocketMine Team
  16. * @link http://www.pocketmine.net/
  17. *
  18. *
  19. */
  20. namespace pocketmine\utils;
  21. use pocketmine\scheduler\FileWriteTask;
  22. use pocketmine\Server;
  23. /**
  24. * Class Config
  25. *
  26. * Config Class for simple config manipulation of multiple formats.
  27. */
  28. class Config{
  29. const DETECT = -1; //Detect by file extension
  30. const PROPERTIES = 0; // .properties
  31. const CNF = Config::PROPERTIES; // .cnf
  32. const JSON = 1; // .js, .json
  33. const YAML = 2; // .yml, .yaml
  34. //const EXPORT = 3; // .export, .xport
  35. const SERIALIZED = 4; // .sl
  36. const ENUM = 5; // .txt, .list, .enum
  37. const ENUMERATION = Config::ENUM;
  38. /** @var array */
  39. private $config = [];
  40. private $nestedCache = [];
  41. /** @var string */
  42. private $file;
  43. /** @var boolean */
  44. private $correct = false;
  45. /** @var integer */
  46. private $type = Config::DETECT;
  47. public static $formats = [
  48. "properties" => Config::PROPERTIES,
  49. "cnf" => Config::CNF,
  50. "conf" => Config::CNF,
  51. "config" => Config::CNF,
  52. "json" => Config::JSON,
  53. "js" => Config::JSON,
  54. "yml" => Config::YAML,
  55. "yaml" => Config::YAML,
  56. //"export" => Config::EXPORT,
  57. //"xport" => Config::EXPORT,
  58. "sl" => Config::SERIALIZED,
  59. "serialize" => Config::SERIALIZED,
  60. "txt" => Config::ENUM,
  61. "list" => Config::ENUM,
  62. "enum" => Config::ENUM,
  63. ];
  64. /**
  65. * @param string $file Path of the file to be loaded
  66. * @param int $type Config type to load, -1 by default (detect)
  67. * @param array $default Array with the default values that will be written to the file if it did not exist
  68. * @param null &$correct Sets correct to true if everything has been loaded correctly
  69. */
  70. public function __construct($file, $type = Config::DETECT, $default = [], &$correct = null){
  71. $this->load($file, $type, $default);
  72. $correct = $this->correct;
  73. }
  74. /**
  75. * Removes all the changes in memory and loads the file again
  76. */
  77. public function reload(){
  78. $this->config = [];
  79. $this->nestedCache = [];
  80. $this->correct = false;
  81. $this->load($this->file, $this->type);
  82. }
  83. /**
  84. * @param $str
  85. *
  86. * @return mixed
  87. */
  88. public static function fixYAMLIndexes($str){
  89. return preg_replace("#^([ ]*)([a-zA-Z_]{1}[ ]*)\\:$#m", "$1\"$2\":", $str);
  90. }
  91. /**
  92. * @param $file
  93. * @param int $type
  94. * @param array $default
  95. *
  96. * @return bool
  97. */
  98. public function load($file, $type = Config::DETECT, $default = []){
  99. $this->correct = true;
  100. $this->type = (int) $type;
  101. $this->file = $file;
  102. if(!is_array($default)){
  103. $default = [];
  104. }
  105. if(!file_exists($file)){
  106. $this->config = $default;
  107. $this->save();
  108. }else{
  109. if($this->type === Config::DETECT){
  110. $extension = explode(".", basename($this->file));
  111. $extension = strtolower(trim(array_pop($extension)));
  112. if(isset(Config::$formats[$extension])){
  113. $this->type = Config::$formats[$extension];
  114. }else{
  115. $this->correct = false;
  116. }
  117. }
  118. if($this->correct === true){
  119. $content = file_get_contents($this->file);
  120. switch($this->type){
  121. case Config::PROPERTIES:
  122. case Config::CNF:
  123. $this->parseProperties($content);
  124. break;
  125. case Config::JSON:
  126. $this->config = json_decode($content, true);
  127. break;
  128. case Config::YAML:
  129. $content = self::fixYAMLIndexes($content);
  130. $this->config = yaml_parse($content);
  131. break;
  132. case Config::SERIALIZED:
  133. $this->config = unserialize($content);
  134. break;
  135. case Config::ENUM:
  136. $this->parseList($content);
  137. break;
  138. default:
  139. $this->correct = false;
  140. return false;
  141. }
  142. if(!is_array($this->config)){
  143. $this->config = $default;
  144. }
  145. if($this->fillDefaults($default, $this->config) > 0){
  146. $this->save();
  147. }
  148. }else{
  149. return false;
  150. }
  151. }
  152. return true;
  153. }
  154. /**
  155. * @return boolean
  156. */
  157. public function check(){
  158. return $this->correct === true;
  159. }
  160. /**
  161. * @param bool $async
  162. *
  163. * @return boolean
  164. */
  165. public function save($async = false){
  166. if($this->correct === true){
  167. try{
  168. $content = null;
  169. switch($this->type){
  170. case Config::PROPERTIES:
  171. case Config::CNF:
  172. $content = $this->writeProperties();
  173. break;
  174. case Config::JSON:
  175. $content = json_encode($this->config, JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING);
  176. break;
  177. case Config::YAML:
  178. $content = yaml_emit($this->config, YAML_UTF8_ENCODING);
  179. break;
  180. case Config::SERIALIZED:
  181. $content = serialize($this->config);
  182. break;
  183. case Config::ENUM:
  184. $content = implode("\r\n", array_keys($this->config));
  185. break;
  186. }
  187. if($async){
  188. Server::getInstance()->getScheduler()->scheduleAsyncTask(new FileWriteTask($this->file, $content));
  189. }else{
  190. file_put_contents($this->file, $content);
  191. }
  192. }catch(\Throwable $e){
  193. $logger = Server::getInstance()->getLogger();
  194. $logger->critical("Could not save Config " . $this->file . ": " . $e->getMessage());
  195. if(\pocketmine\DEBUG > 1 and $logger instanceof MainLogger){
  196. $logger->logException($e);
  197. }
  198. }
  199. return true;
  200. }else{
  201. return false;
  202. }
  203. }
  204. /**
  205. * @param $k
  206. *
  207. * @return boolean|mixed
  208. */
  209. public function __get($k){
  210. return $this->get($k);
  211. }
  212. /**
  213. * @param $k
  214. * @param $v
  215. */
  216. public function __set($k, $v){
  217. $this->set($k, $v);
  218. }
  219. /**
  220. * @param $k
  221. *
  222. * @return boolean
  223. */
  224. public function __isset($k){
  225. return $this->exists($k);
  226. }
  227. /**
  228. * @param $k
  229. */
  230. public function __unset($k){
  231. $this->remove($k);
  232. }
  233. /**
  234. * @param $key
  235. * @param $value
  236. */
  237. public function setNested($key, $value){
  238. $vars = explode(".", $key);
  239. $base = array_shift($vars);
  240. if(!isset($this->config[$base])){
  241. $this->config[$base] = [];
  242. }
  243. $base =& $this->config[$base];
  244. while(count($vars) > 0){
  245. $baseKey = array_shift($vars);
  246. if(!isset($base[$baseKey])){
  247. $base[$baseKey] = [];
  248. }
  249. $base =& $base[$baseKey];
  250. }
  251. $base = $value;
  252. $this->nestedCache[$key] = $value;
  253. }
  254. /**
  255. * @param $key
  256. * @param mixed $default
  257. *
  258. * @return mixed
  259. */
  260. public function getNested($key, $default = null){
  261. if(isset($this->nestedCache[$key])){
  262. return $this->nestedCache[$key];
  263. }
  264. $vars = explode(".", $key);
  265. $base = array_shift($vars);
  266. if(isset($this->config[$base])){
  267. $base = $this->config[$base];
  268. }else{
  269. return $default;
  270. }
  271. while(count($vars) > 0){
  272. $baseKey = array_shift($vars);
  273. if(is_array($base) and isset($base[$baseKey])){
  274. $base = $base[$baseKey];
  275. }else{
  276. return $default;
  277. }
  278. }
  279. return $this->nestedCache[$key] = $base;
  280. }
  281. /**
  282. * @param $k
  283. * @param mixed $default
  284. *
  285. * @return boolean|mixed
  286. */
  287. public function get($k, $default = false){
  288. return ($this->correct and isset($this->config[$k])) ? $this->config[$k] : $default;
  289. }
  290. /**
  291. * @param string $k key to be set
  292. * @param mixed $v value to set key
  293. */
  294. public function set($k, $v = true){
  295. $this->config[$k] = $v;
  296. foreach($this->nestedCache as $nestedKey => $nvalue){
  297. if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){
  298. unset($this->nestedCache[$nestedKey]);
  299. }
  300. }
  301. }
  302. /**
  303. * @param array $v
  304. */
  305. public function setAll($v){
  306. $this->config = $v;
  307. }
  308. /**
  309. * @param $k
  310. * @param bool $lowercase If set, searches Config in single-case / lowercase.
  311. *
  312. * @return boolean
  313. */
  314. public function exists($k, $lowercase = false){
  315. if($lowercase === true){
  316. $k = strtolower($k); //Convert requested key to lower
  317. $array = array_change_key_case($this->config, CASE_LOWER); //Change all keys in array to lower
  318. return isset($array[$k]); //Find $k in modified array
  319. }else{
  320. return isset($this->config[$k]);
  321. }
  322. }
  323. /**
  324. * @param $k
  325. */
  326. public function remove($k){
  327. unset($this->config[$k]);
  328. }
  329. /**
  330. * @param bool $keys
  331. *
  332. * @return array
  333. */
  334. public function getAll($keys = false){
  335. return ($keys === true ? array_keys($this->config) : $this->config);
  336. }
  337. /**
  338. * @param array $defaults
  339. */
  340. public function setDefaults(array $defaults){
  341. $this->fillDefaults($defaults, $this->config);
  342. }
  343. /**
  344. * @param $default
  345. * @param $data
  346. *
  347. * @return integer
  348. */
  349. private function fillDefaults($default, &$data){
  350. $changed = 0;
  351. foreach($default as $k => $v){
  352. if(is_array($v)){
  353. if(!isset($data[$k]) or !is_array($data[$k])){
  354. $data[$k] = [];
  355. }
  356. $changed += $this->fillDefaults($v, $data[$k]);
  357. }elseif(!isset($data[$k])){
  358. $data[$k] = $v;
  359. ++$changed;
  360. }
  361. }
  362. return $changed;
  363. }
  364. /**
  365. * @param $content
  366. */
  367. private function parseList($content){
  368. foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
  369. $v = trim($v);
  370. if($v == ""){
  371. continue;
  372. }
  373. $this->config[$v] = true;
  374. }
  375. }
  376. /**
  377. * @return string
  378. */
  379. private function writeProperties(){
  380. $content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n";
  381. foreach($this->config as $k => $v){
  382. if(is_bool($v) === true){
  383. $v = $v === true ? "on" : "off";
  384. }elseif(is_array($v)){
  385. $v = implode(";", $v);
  386. }
  387. $content .= $k . "=" . $v . "\r\n";
  388. }
  389. return $content;
  390. }
  391. /**
  392. * @param $content
  393. */
  394. private function parseProperties($content){
  395. if(preg_match_all('/([a-zA-Z0-9\-_\.]*)=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches
  396. foreach($matches[1] as $i => $k){
  397. $v = trim($matches[2][$i]);
  398. switch(strtolower($v)){
  399. case "on":
  400. case "true":
  401. case "yes":
  402. $v = true;
  403. break;
  404. case "off":
  405. case "false":
  406. case "no":
  407. $v = false;
  408. break;
  409. }
  410. if(isset($this->config[$k])){
  411. MainLogger::getLogger()->debug("[Config] Repeated property " . $k . " on file " . $this->file);
  412. }
  413. $this->config[$k] = $v;
  414. }
  415. }
  416. }
  417. }